Reputation: 7524
I am not able to understand certain behaviour exhibited by git.
I have a local branch, say myBranch, which is in sync with remote branch on gitlab by same name. Infact, I had first created the branch locally, and then pushed it to remote using:
git push -u origin myBranch
I understand it also means that origin/myBranch is also at same commit, say C1, as myBranch(I hope I am correct here).
Now I make some local changes and then add them using amend.
git commit --amend --no-edit.
So I am amending existing commit C1, and I expect both myBranch and origin/myBranch should point to C1.
But when I do git status, I get:
On branch myBranch
Your branch and 'origin/myBranch' have diverged, and have 1 and 1 different commits each, respectively. (use "git pull" to merge the remote branch into yours)
Why should I get this when both myBranch and origin/myBranch are pointing to C1?
Upvotes: 0
Views: 100
Reputation: 489888
The --amend
option seems like it changes an existing commit. This is not the case.
I think the best illustration here is to draw (part of) the commit graph. Remember that in Git, each commit has a unique hash ID—a big ugly string of letters and numbers. Every commit has its own separate ID. These things are too unwieldy for humans to use regularly, so we tend to use names, such as branch names, to remember them:
...--F--G--H <-- myBranch
When you make a new commit, you do it by first doing a git checkout
of the desired branch. This attaches the special name HEAD
to that branch name, and extracts the specific commit—in this case H
—to which the name points.
You then make whatever changes you want (in your work-tree), use git add
(to copy the updated files back into the index / staging-area), and run git commit
(without --amend
). At this point, Git:
H
) into the new commit as its parent.So now we have:
...--F--G--H <-- myBranch (HEAD)
\
I
where I
is the new commit. The last step is the tricky part: your Git now writes the new commit's hash ID (which was just created by creating the commit—we're calling it I
here but again it's some big ugly hash ID) into the name myBranch
, as that's the one that the special name HEAD
is attached to:
...--F--G--H
\
I <-- myBranch (HEAD)
If you now git push
this to the other Git that you are calling origin
, your Git remembers that their Git's myBranch
is pointing to commit I
:
...--F--G--H
\
I <-- myBranch (HEAD), origin/myBranch
But now you decide that there's something wrong with commit I
. So you make some changes to files and git add
them, and run git commit --amend
. This does not change commit I
. Instead, it creates another new commit—let's call it J
—but this time, instead of setting J
's parent to I
, Git sets J
's parent to H
again. Then it writes J
's actual hash ID, whatever that is, into myBranch
:
J <-- myBranch (HEAD)
/
...--F--G--H
\
I <-- origin/myBranch
This is what is in your repository. The other repository, over at origin
, has its myBranch
name pointing to commit I
—the one you gave it earlier.
You must now correct the other Git repository's idea of where its myBranch
should point. You must deliver commit J
to it, then tell it: forget commit I
, just make your myBranch
name commit J
. Once you do that, both your repository and their repository will no longer have a way to find commit I
at all. Your Git will have this:
J <-- myBranch (HEAD), origin/myBranch
/
...--F--G--H
\
I [abandoned]
Note that commit I
is not yet gone. It will stick around for a while—at least 30 days by default—in case you decide that you would like it back. It will, however, be hard to find. I've used I
to stand in for its actual hash ID. But what is its actual hash ID? How will you ever guess what its hash ID might be? (There is a way, but save that for another question :-) )
As Andy said in his answer, you will need to use git push -f
to force the server to forget commit I
while remembering new replacement commit J
. This is fine as long as everyone who might use the server's myBranch
name agrees in advance that these names are manipulated this way. If you're the only one using the server repository, you must agree with yourself that this is OK. If other people are using it, make sure they understand what you're doing first!
Upvotes: 2
Reputation: 30418
Adding a commit to your local branch does not automatically update the remote tracking branch. In order to update the remote tracking branch with any additional commits in the local branch, use git push
(assuming that you set up the remote tracking branch already, which you did when specifying -u
for the initial git push
).
As an aside, in generate you should avoid amending commits on branches that you've pushed to a remote repository. When you amend a commit, it changes the commit object to include whatever changes you're amending. If you try to push this branch using git push
, you'll get an error saying the push was rejected because the tip of your branch is behind its remote counterpart.
The reason for that is that Git uses cryptographic SHA-1 hashes to uniquely identify every commit (you can see them in the commit history, for example by executing git log
), and some of the commit metadata such as message and timestamp are also being hashed along with the commit's contents. Essentially, when you amended that commit you created a new commit, with a different hash, and replaced the old one with it. Thus, your local branch is pointing to that commit and is behind the remote because it doesn't contain the old one, and the remote branch is behind your local branch because it doesn't contain the new one.
You'll need to force push (git push -f
) in order to push your changes to the remote repository - replacing the commit the remote branch points to with the new one as well. The problem with that is if any other developer has pulled that branch to their local machine, it is very hard for them to sync their changes since you changed the branch out from under them.
Amending commits and rebasing is fine for local or private branches, but in general you should avoid doing this for any branch that another developer could have pulled down to their machine.
Upvotes: 4
Reputation: 95038
So I am amending existing commit C1, and I expect both myBranch and origin/myBranch should point to C1.
You've edited a commit at branch C1
and git
moved the branch to the new commit. There is no reason to expect git
would move another branch, local or remote-tracking.
The reference origin/myBranch
(it's not exactly a branch, it's a reference) stays at the old commit because the remote-tracking reference only moves when you fetch/pull/push. The very idea of the remote-tracking references is to remember where their upstream remote branches were when you communicated with the remote repository.
Upvotes: 1