What's the use of the staging area in Git?

前端 未结 5 945
青春惊慌失措
青春惊慌失措 2021-02-01 14:54

What is the point of git add . or git add to add it to the staging area? Why not just git commit -m \"blabla\"?

5条回答
  •  有刺的猬
    2021-02-01 15:41

    It's worth comparing how Git handles this—Git makes you know about and use the staging-area—to how Mercurial handles this. In Mercurial, you work exactly as you suggest: you just run hg commit and Mercurial figures out what you changed and commits it. You do have to hg add a new file, but if you are just changing existing files, there is nothing special to do: you change them, and commit, and you are done.

    Mercurial's behavior seems (and in my observation, has been) much more new-user-friendly. Git actually lets you get most of the same effect by using git commit -a. That is, you just add -a to whatever other options you will use, and Git will do pretty much the same thing as Mercurial. But this is kind of a crutch, because eventually, you will find something that Git has done that is quite inexplicable unless you know about the staging area.

    Hidd3N's answer shows a number of ways you can use Git's staging area. But if you step back a bit, and compare Mercurial and Git, you can, I think, see a lot more of what is really going on.

    Remember that the job of any version control system (VCS) is to let you retrieve every committed version ever. (And, since both Git and Mercurial work on the snapshot of whole system basis, they are easy to compare here. There are some much older VCSes that operate on one file at a time: you must specifically check-in / commit each individual file. Git and Mercurial make a snapshot of everything-all-at-once.) These committed snapshots should last forever, and never change at all. That is, they are read-only.

    Files that are read-only are no good for working on, though. So any VCS must have, somehow / somewhere, two separate things:

    • the place where you work on files: this is your work-tree; and
    • the place that snapshots are stored: this is your version database, or repository, or some other word—Git calls these things objects while Mercurial has a more complicated set of structures, so let's just call them objects here.

    Git's object storage area has a bunch of read-only objects: in fact, one for every file, and every commit, and so on. You can add new objects any time, but you cannot change any existing objects.

    As Mercurial demonstrates, there is no requirement for a separate staging area: the VCS can use the work-tree as the proposed commit. When you run hg commit, Mercurial packages up the work-tree and makes a commit from it. When you make changes in the work-tree, you change the proposed next commit. The hg status command shows you what you're proposing to commit, which is: whatever is different between the current commit and the work-tree.

    Git, however, chooses to interpose this intermediate area, halfway between the read-only commits and the read/write work-tree. This intermediate area, the staging area or index or cache, contains the proposed next commit.

    You start out by checking out some commit. At this point, you have three copies of every file:

    • One is in the current commit (which Git can always find by the name HEAD). This one is read-only; you can't change it. It's in a special, compressed (sometimes very compressed), Git-only form.
    • One is in the index / staging-area. This one matches the HEAD one now, but it can be changed. It's the one proposed to go into the next commit. This, too, is in the special Git-only form.
    • The last one is in your work-tree, in ordinary form where you can work on it.

    What git add does is to copy files from your work-tree, into the staging area, overwriting the one that used to match the HEAD commit.

    When you run git status, it must make two separate comparisons. One compares the HEAD commit to the index / staging-area, to see what's going to be different in the next commit. This is what's to be committed. The second comparison finds what's different between the index / staging-area, and the work-tree. This is what's not staged for commit.

    When you run git commit -a, Git simply does the copy-to-staging-area based on the second comparison. More precisely, it runs the equivalent of git add -u. (It secretly does this with a temporary staging-area, in case the commit fails for some reason, so that your regular staging-area / index is undisturbed for the duration of the commit. Some of this depends on additional git commit arguments as well. Normally this tends to be invisible, at least until you start writing complex commit hooks.)

提交回复
热议问题