19

I clearly do not understand git at all. This is what I'm getting:

git branch  (outputs that I'm on master)
git checkout -b foo
echo "next line" >> file (file is an existing file)
git add file (stages)
git checkout master
git status (shows that file has "next line" and is staged!!)
git commit (commits the changes that were staged on branch foo!!)
git checkout foo

Here is the kicker. foo now doesn't show any changes made to file in the working directory OR staged.

So looks like - any changes you make, including modifying files and staging, happen to ALL branches. and when you COMMIT to a specific branch, those changes are discarded on all other branches except the one you committed on.

Is this actually what is going on? Can someone make this make sense to me? It sounds like completely screwy behavior and clearly I don't get the design idea that makes this a sensible thing to do.

Edit for explicit example:

$ mkdir element
$ cd element
$ git init
Initialized empty Git repository in /home/dan/element/.git/
$ echo "one" >> one
$ git add one
$ git commit -m msg
[master (root-commit) 36dc8b0] msg
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 one
$ git checkout -b fire
Switched to a new branch 'fire'
$ echo "next line" >> one
$ git checkout master
M       one
Switched to branch 'master'
$ cat one
one
next line
$

Which patently contradicts this from the git pro book:

This is an important point to remember: Git resets your working directory to look like the snapshot of the commit that the branch you check out points to. It adds, removes, and modifies files automatically to make sure your working copy is what the branch looked like on your last commit to it.

djechlin
  • 59,258
  • 35
  • 162
  • 290
  • 2
    Does my answer help clarify things? I think it predates your edit, but I'm not entirely sure. – larsks May 11 '12 at 17:14

2 Answers2

17

It doesn't matter what branch you're on when you add a file, only when you commit it. So if you do this:

git add file
git checkout master
git commit

You have committed the file to the master branch.

Here's a complete example, with output. We start with a new repository:

$ git init
Initialized empty Git repository in /home/lars/tmp/so/repo/.git/

At this point, we're on the master branch and we haven't yet added any files. Let's add a file:

$ date > file1
$ cat file1
Fri May 11 13:05:59 EDT 2012
$ git add file1
$ git commit -m 'added a file'
[master (root-commit) b0764b9] added a file
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 file1

Great, we now have a branch (master) with one commit. Let's create the new branch:

$ git checkout -b foo
Switched to a new branch 'foo'
$ git branch
* foo
  master
$ ls
file1

Now we'll add a line to file1.

$ date >> file1
$ git status
# On branch foo
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   file1
#
no changes added to commit (use "git add" and/or "git commit -a")

This shows that the file has been modified, but not yet staged. Let's stage the file and commit it:

$ git add file1
$ git commit -m 'made a change'
[foo 761bed9] made a change
 1 files changed, 1 insertions(+), 0 deletions(-)

And re-run git status:

$ git status
# On branch foo
nothing to commit (working directory clean)

At this point, the file looks like this:

Fri May 11 13:05:59 EDT 2012
Fri May 11 13:07:36 EDT 2012

If we switch back to the master branch, we'll see the earlier version of the file without the second line:

$ git checkout master
Switched to branch 'master'
$ cat file1
Fri May 11 13:05:59 EDT 2012

Changes to a file are isolated to the branch on which they were committed.

In your updated example, this...

$ git checkout master

...does not generate an error because at this point, the version of 'one' in both master and fire is identical. The changes in the working directory would apply equally well to either version.

Nicekiwi
  • 4,567
  • 11
  • 49
  • 88
larsks
  • 277,717
  • 41
  • 399
  • 399
  • 1
    Yes - if you work on a branch atomically with respect to commits everything works as in this example and it makes sense to me. If you modify and possibly stage without committing and then switch branches, then you enter this weird loopy state that happened in my first post. Is the moral simply never switch branches without committing? However: if I do this in the other direction and work on master and switch to branch fire, it gives me an error: `error: You have local changes to 'one'; cannot switch branches.` Don't know why I didn't get this error working the other direction. – djechlin May 11 '12 at 17:16
  • 1
    Was the file actually being tracked prior to running `git add`? Can you retool your question using a sample repository showing the actual commands and output? In general, it is common to either commit or [stash](http://git-scm.com/book/en/Git-Tools-Stashing) your changes before switching branches. – larsks May 11 '12 at 17:23
  • 1
    Edited question to show the full dialogue instead of just the commands. I'll look into stash - it seems I'm not understanding how "shallow" branches really are in git. I don't understand why git let me make a dirty checkout though. – djechlin May 11 '12 at 17:35
  • I think I see. As long as the branches point to the same working directory you can freely switch. Once you commit one and they point to different snapshots git won't let you make dirty checkouts. – djechlin May 11 '12 at 18:20
9

The staging area aka index is common to all branches, which explains your observation

Aadith Ramia
  • 10,005
  • 19
  • 67
  • 86