0

I am confused about what the git push does.

  1. It will push all the committed files to the central repository only ? what about the rest files that also managed by Git but not been changed, are they also been pushed to the central repository or just the committed ones will be pushed.

  2. If just the committed files been pushed, how come git can detect the conflict if there is a change of another file -- hello.html made by another team member after I pulled but before I pushed. (I never change that hello.html file).

  3. After the git push, will the working directory, local repository, central repository be consistent? meaning having all the same content?

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
jiaqi
  • 11
  • 2
  • See http://stackoverflow.com/questions/26005031/what-does-git-push-do-exactly (answers parts 1 and 2 indirectly). Part 3 is more complicated... – torek Nov 14 '16 at 04:28

3 Answers3

1
  1. Git push sends commits to the remote repository, not files. So what it will send depends on what you passed to git commit previously. Uncommitted files are not sent to the remote repository.

  2. Git detects when the remote repository has a change and also you have a change, even if you have not committed yours.

  3. The working directory is not relevant to git push. Only the committed contents of the local and remote repositories are relevant. The two repositories will typically have the same commits after a push, but your local working directory could have other stuff that you haven't committed.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • thanks! that is helpful, Can I get a further explanation about what is the meaning of "sends commits not files" – jiaqi Nov 14 '16 at 04:25
  • @jiaqi: Sure: http://stackoverflow.com/questions/2745076/what-are-the-differences-between-git-commit-and-git-push – John Zwinck Nov 14 '16 at 04:31
1

(On re-reading your question, I might have answered something you did not actually intend to ask! It's all about what happens to the other Git's working tree, if there is one.)

John Zwinck's answer is correct. I have (much) more in my answer to What does GIT PUSH do exactly? on this part, about how git push pushes commits, not files (which makes your work-tree irrelevant). Please read that to see what the other Git on the remote does with your git push (and remember that that other Git has the right to either accept your push reference update request(s), or reject one, or many, or all of them).

Part 3—what happens to the working directory for the receiving Git—is the trickiest. It depends on the configuration set up on that other Git. It also depends on the version of the other Git, because there is a configuration that only works in newer versions.

First, let's assume that the remote Git accepts your push ref-update, because if it rejects it, well, nothing happens.1 If nothing happens, the question of "what happens" is pretty boring! So the other Git has accepted your push, and has some new commits in its repository,2 and has a reference—a branch or tag name—pointing to one of those new commits. If this is a new branch, or a new tag, then the receiving Git repository just has a new branch or tag, and any existing branch or tag is not affected, and again the question is kind of boring: there is clearly no need to change anything in the working tree.

Next, we need to talk-about a so-called bare repository.

You already know that a Git repository has two parts of interest here, and it's important to mention the third:

  1. The repository itself, where all commits, and everything they need, are stored forever, in a format usable only by Git itself, so that you can git checkout any particular branch, or git checkout any older commit to see what was in that—this gives you the dreaded "detached HEAD" after which you must git checkout some branch again to re-attach your HEAD. You can also git checkout a particular tag, which also gives you a detached HEAD.3 Git's repository objects (commits, plus the other items commits use to store files) within the repository are permanent and unchanging, and Git can only ever add to to them.4

  2. The work tree or working tree (two names for the same thing): normal, computer- and human-accessible files with normal names, where you can view and edit the files, run your program or app, work with your web page, or whatever it is that you are doing with Git.

  3. The index or staging-area (again, two names for the same thing—though there's a third name, the cache, as in git diff --cached, which does exactly the same thing as git diff --staged). The index is mostly "what will go into the next commit": after you edit a file in the work-tree, you git add the file. This copies its new contents into the index,5 so that it is ready to go into the next commit. Otherwise the next commit you make will save the file the way it was/is in the current commit, i.e., the way it is right now in the index.

    (The index also has a big and important role in doing merges, but this is for another topic. It has a smaller role in making Git as fast as Git is, which is where the "cache" aspect comes in.)

Now, a bare repository is quite simply a Git repository without a work tree. Since it has no work tree, there's nothing to worry about here. You can push to any branch in a bare repository (provided you meet whatever requirements that Git sets for accepting pushes).

So that leaves us with a non-bare ("regular"?) Git repository, to which you are pushing, that accepts your push. What happens to its work tree? Well, now we're back to: "that depends."

All Git repositories—including bare ones, for that matter—still have the notion of the current branch. The current branch is the one you set with git checkout branchname. In most repositories it starts out as master, and you git checkout some other branch to change it as needed.

All Git repositories also have configurations. One of the configuration items is receive.denyCurrentBranch. This can be set to true, refuse, false, warn, ignore, or—in newer Git versions—updateInstead. The default, if you do not set anything, is to pretend it is set to refuse. Each setting results in different behavior:

  • If the setting is refuse or true, the receiving Git simply automatically rejects any attempt to push to its current branch. The update request for this one branch is rejected. That makes the problem go away. Update requests within the same push, but for other branches, may still be accepted, of course.

  • If the setting is false, ignore, or warn, the receiving Git accepts the update, but does not update the working tree (nor the index). They become "out of sync", which is generally bad; but it's up to whoever manages that Git to deal with it. If the setting is warn, then you (the one doing the push) get a warning message about this, which does them (the people running the receiving Git) not one bit of good.6

  • If the setting is updateInstead—available only since Git version 2.3—then the receiving Git actually checks: "is the branch all checked-in nicely?" If so, it allows the update and does a full checkout, so that the index and work-tree get synchronized. If not—if either the index, or the work-tree, have uncommitted changes—it refuses the push.

There's another tricky case here, available in versions of Git at 2.5 or higher: you can now have more than one work-tree associated with a repository. Each work-tree gets its own independent index, and its own current branch. How a receiving Git treats these, I don't know. Logically, "refuse" modes should refuse the update if any associated work-tree has that branch as its current branch, and "updateInstead" modes should update whichever work-tree has that branch, but I have not tested this.

That, I think, covers all the cases available today. Most central repositories are bare, so that the current branch on that repository is irrelevant. (Note as well that none of this affects any post-receive deploy scripts, some of which will automatically check out certain branches to special alternative work-trees, overriding the "bare-ness". But this process is separate from Git itself, and is entirely up to the post-receive hook.)


1As usual, the complete truth is even more complicated: the other end could reject your push through a pre-receive or update hook, while—via that hook—making something happen. But that's quite unlikely, so let's just ignore it.

2In a few cases—the classic examples are a force-push to "retract" a commit, or a simple lightweight-tag push to tag some existing commit—there's no new commit, just a change to a branch name, or the addition of a new tag.

3A work-tree with a "detached HEAD" is on no branch. However, you can still make new commits in it. When you do so, they effectively go into an anonymous (unnamed) branch. Because that branch has no name, it doesn't enter into the rest of this discussion: A push request that succeeds updates some reference name, and this no-branch / anonymous-branch "detached HEAD" work-tree has no name, so it cannot be updated. (You could say "it has the name HEAD": which is true, but HEAD is not a branch name. Moreover, because it lives outside the refs/ name-space, it can never be pushed-to.)

4There's one exception to this "only ever add new things" rule. Some objects are meant to be temporary, so while no object can ever change, Git does have a mechanism for getting rid of obsolete, unused objects. That's Git's "garbage collector", or git gc. You can also use this to discard unwanted commits, by rewinding a branch so that it no longer refers to them. Such commits become "abandoned" and are eventually—not immediately—swept away.

(Note that they're never changed, only ghosted, and then eventually deleted entirely. The "ghosts" can be found through reflogs, until the reflog entries expire, and then until git gc cleans them out they can be found even more painfully with git fsck, which is a lot like git gc except that it checks everything extra-carefully, and optionally restores lost items, instead of discarding them.)

5In fact, rather than copying the entire file into the index, what Git does is store the file's hash ID in the index. Running git add actually sticks the file into the repository, as a "blob" object. Git then puts the blob's ID into the index. If you update and git add a file, then update and git add again, you get an unreferenced ("dangling", in git fsck terms) blob, which the garbage collector eventually reaps; but until then, it's actually possible to recover the added version.

6The warn mode is really intended for the case where you are in control of the receiving repository. It reminds you to go log in to the other machine if necessary, then go over to that repository and take care of the problem.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
1

Git manages project history using 4 types of objects:

  • blob: represents a file, containing compressed entire contents of a file.
  • tree: represents a directory, containing references to blobs and trees.
  • commit: represents a commit, containing a commit message, date, a reference to a tree that represents the root directory, etc.
  • tag: represents a tag, containing an annotation of a tag, a reference to a commit, etc.

When you commit some changes, Git creates brand-new blobs for each of the changed files and a commit object.

When a push, Git sends objects which are not in the central repository but are in your repository.

So,

  1. Yes, only committed files (blobs) are sent. After you make some commits, your repository contains new blobs and commit objects which are not in the central repository. They are all sent to the central but no others are sent.

  2. Git compares commit objects between the central and yours. It checks whether your commits are on the tip of the commit history in the central before a push and if not it rejects the push.

  3. No. If others pushed some objects to the central, your repository will never have the objects until a fetch. And your working directory may contains changes that are not in even your repository (i.e. not committed changes.)

janos
  • 120,954
  • 29
  • 226
  • 236
kaitoy
  • 1,545
  • 9
  • 16