Hanfei Sun
Hanfei Sun

Reputation: 47051

What does the shell script in git-stash mean and how does it work?

I saw code snippets like this in git-stash

            rm -f "$TMP-index" &&
            GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&


            # find out what the user wants
            GIT_INDEX_FILE="$TMP-index" \
                    git add--interactive --patch=stash -- &&

            # state of the working tree
            w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
            die "$(gettext "Cannot save the current worktree state")"

            git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
            test -s "$TMP-patch" ||
            die "$(gettext "No changes selected")"

            rm -f "$TMP-index" ||
            die "$(gettext "Cannot remove temporary index (can't happen)")"

What I don't understand is the expression like this:

GIT_INDEX_FILE="$TMP-index" git read-tree HEAD

Does that mean assign the value of TMP-index to GIT_INDEX_FILE, and then execute command git read-tree HEAD?

I'm not sure about that. So I tried to write a similar command like this.

A="1" ls
echo $A

The value of A is still null.

I also tried this:

echo $a
=> 1
k=$(a=100 echo $a)
=> 
echo $k
=> 1

It looks that the value of a is not changed at all. So the a=100 seems useless..

Does anyone have ideas about syntax like this?

Upvotes: 1

Views: 669

Answers (3)

Richard Hansen
Richard Hansen

Reputation: 54143

Does that mean assign the value of TMP-index to GIT_INDEX_FILE, and then execute command git read-tree HEAD?

Not quite. The syntax:

VAR1=val1 VAR2=val2 somecommand arg1 arg2

tells the shell to execute the command somecommand with arguments arg1 and arg2, but somecommand's environment will have VAR1 set to val1 and VAR2 set to val2. The shell itself won't be affected by those assignments, only the environment of somecommand. The effect is equivalent to:

env VAR1=val1 VAR2=val2 somecommand arg1 arg2

The only difference is that the former method does not require finding and executing the /usr/bin/env utility (I'm assuming env is not a shell built-in utility), so it's a hair faster.

It is also equivalent to:

(export VAR1=val1 VAR2=val2; somecommand arg1 arg2)

Notice the parentheses -- this causes the shell to execute the commands in a separate subshell. Any changes to a subshell (such as variable assignments) do not persist when the subshell ends.

Example

#!/bin/sh
FOO=value1
printf %s\\n "In the shell, FOO is ${FOO} (before running python)"
FOO=value2 python -c 'import os; print "In python, FOO is", os.environ["FOO"]'
printf %s\\n "In the shell, FOO is ${FOO} (after running python)"

The above script prints the following output:

In the shell FOO is value1 (before running python)
In python, FOO is value2
In the shell, FOO is value1 (after running python)

What Git is doing

In the snippet of Git shell code you provided, git stash is temporarily changing GIT_INDEX_FILE for a few invocations of git so that stash can perform some index-mutating operations without messing up your index:

  • First, git stash uses git read-tree to create the temporary index file and initialize its contents to whatever is in HEAD.
  • Second, git stash uses git add --interactive to ask the user which parts to stash and saves the chosen changes in the temporary index file.
  • Third, git stash uses git write-tree to save the contents of the temporary index as a tree object in your Git repository.
  • Fourth, git stash compares the tree object to HEAD to make sure you actually selected something to stash.
  • Finally, git stash deletes the temporary index file it created.

Upvotes: 1

josinalvo
josinalvo

Reputation: 1488

In BASH, there are some issues with defining a variable and running a new process afterward.

If you just define the variable, it is not 'inherited' by the new processes

Lets define a variable

$ VAR='value'
$ echo $VAR
value

And then go into a new process

$ bash

The var is undefined

$ echo $VAR

$

But once we get back

$ exit

the var is defined again

$ echo $VAR
value
$ 

the usual way around this is to use export

$ export VAR2='yep'
$ bash
$ echo $VAR2
yep
$ 

what you are seeing (seems to be) an alternate syntax for that, that assigns the variable in the child process, but not in the original process

$ var3='wow' bash  #this line opens a new bash, and assigns the variable for this new bash
$ echo $var3
wow
$ exit
$ echo $var3

$ 

Upvotes: 1

DigitalRoss
DigitalRoss

Reputation: 146043

The answer to your question "Does that mean..." is yes.

However, the variable expansion happens when the command is parsed, which would be too early to see the result of setting an environment variable and then importing it into a shell, and that's why you aren't getting the results you expect.

There are various ways to see the result of a one-line environment variable expression; you could observe...

$ B=100 env | grep B
B=100

...or...

$ z=abc sh -c 'echo $z'
abc

Upvotes: 1

Related Questions