0

Suppose we have a branchA. The last commit in its remote version was by me, say c1. Then somebody did some changes (based on a patch that I provided) and pushed on the remote (or rather "updated" my commit c1). Now I wanted to sync the remote with my local branch, and I did git fetch origin but then I saw the message

Your branch and 'origin/branchA' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
nothing to commit, working tree clean

The way that I solved it was by executing git reset --hard HEAD~1 and then I run git fetch origin && git merge which brought me to the desired position of being in sync with the remote. My question is: was that a correct handling? If not, what would be the optimal?

hal
  • 105
  • 1
  • 3
  • `git reset --hard HEAD~1` was unnecessary (or even harmful, you lost your most recent local commit), `fetch` & `merge` is the correct method (which is equivalent to `pull`, as is suggested in the message). – mkrieger1 Nov 23 '20 at 20:05
  • Does this answer your question? [How to sync with a remote Git repository?](https://stackoverflow.com/questions/4313125/how-to-sync-with-a-remote-git-repository) – mkrieger1 Nov 23 '20 at 20:06
  • Well no, because then I would have to make a new commit with the changes. I *didn't* want to make a new commit. I wanted to "update" my last commit. – hal Nov 23 '20 at 20:15
  • I'm not sure if I understand your situation exactly. But apparently you have the desired result so you did it correctly. – mkrieger1 Nov 23 '20 at 20:17

1 Answers1

0

Nobody can update any commit. That means they didn't, and you won't either.

You can, however, have the illusion of updating some commit. It's an illusion! It did not really happen! Always remember that, otherwise none of this makes any sense.

Git finds a commit by hash ID. The hash ID of some commit depends on the exact content of the commit. Once you, or whoever, has made the commit, that hash ID means that commit. It means that now. It means that tomorrow. It means that next year. It always means that commit, and no other commit, ever.

Humans, however, don't use hash IDs. (Quick: what are the hash IDs of the last ten commits you made? I don't know any of mine either. I don't even remember the special empty tree hash ID.) Humans use branch names: I made a commit on branchA. That's what you said, more or less.

We have Git store hash IDs in branch names. Your branchA stored the hash ID of your commit. Then you gave your commit, with its permanently-assigned forever ID, to some other Git in charge of some other repository. Because every Git computes hash IDs the same way, their hash ID for that commit was the same hash ID. They put that in their repository and it had the same hash ID and they set up their name branchA to record the same big ugly hash ID.

Later, someone—not you—came along and squinted hard at that particular commit and said something like: Hah, I can do better! They took that commit off of their branchA—the branchA that is in that other Git repository that you have your Git talk to; your Git uses the name origin/branchA for this—using some Git command, perhaps git reset --hard HEAD~1. We don't know, or really care for that matter, exactly what Git command they used.

Having ripped that commit off that branch—it's still there in their Git repository, it's just not at the end of that branch now—they then added their "new and improved" version of that commit to their branchA. We can draw that situation, that occurs in their Git repository, like this:

          H   <-- ???
         /
...--F--G--I   <-- branchA

The uppercase letters here stand in for the actual Git hash IDs (which we don't know, or really care about that much since we never memorize any of them). Your commit H, which used to be the last commit on branchA, is now floating free in space, with no name that finds it. Their new-and-improved version of your commit, which we're calling I, is now the last commit on their branchA.

This is not how Git is supposed to be used

Suppose for a moment that they had not torn your commit out by the roots, and instead, just added a commit. (This is how Git is supposed to be used, in general.) If they had done that, their Git repository would now have this sequence of commits in it:

...--F--G--H--I   <-- branchA

That is, they'd have a new commit I, but the parent of commit I would be your commit H. Their name branchA would remember the hash ID of their commit I, and their commit I would remember the hash ID of your commit H.

You would then be able to run:

git fetch origin

which, in your own Git repository, would result in this:

...--F--G--H   <-- branchA
            \
             I   <-- origin/branchA

Remember, your Git uses the name origin/branchA to remember the hash ID they're remembering with their name branchA. The things that actually matter to Git are the commits with their commit hash IDs, but Git gives us these names to help us find the hash IDs. Each repository has its own names: their name branchA is for them to use, and your name branchA is for you to use.

Had they added a commit on like this, you could then have your Git "slide your name forward" (and down) so that you would have, in your repository:

...--F--G--H
            \
             I   <-- branchA, origin/branchA

Your Git and their Git would now both agree that the last commit in the branch named branchA is commit I (whatever its actual hash ID is). Your Git would hang on to your own name branchA and your name origin/branchA, but both would now point to existing commit I.

If you need to fix up commit I, you simply add a new commit J to your repository:

...--F--G--H--I   <-- origin/branchA
               \
                J   <-- branchA

Note how your new commit points back to the (now-shared) existing commit I, which adds on. You can now have your Git send new commit J back, and ask them to set their branchA name to point to commit J.

Faking it: the illusion of changing or removing commits

This is the normal way to use Git: everyone adds commits to the collection, and nobody takes any way. Some people find this messy (because it is messy) and prefer to fake things as if the development happened much more cleanly. There's nothing really wrong with that. If commits H and I have no real value, and only commit J should remain, we don't need to preserve H and I for all time: we can make a new and improved commit that's like J, but that has G as its parent:

          H--I--J   <- ???
         /
...--F--G--J'  <-- branchA

(in your Git repository, with origin/branchA not shown). Suppose we now send commit J' to the other Git, and force it to set its name branchA to point to J'. Then they, too, will have:

          H--I--J   <-- ???
         /
...--F--G--J'  <-- branchA

in their Git repository. Anyone who looks up commits by name only, and uses the name branchA, will see commit J', and from J', will reach back to commit G. It is as if commits H and I and J never happened.

If you have the hash IDs of commits H, I, and J memorized, or written down on paper or a whiteboard or something, you can still find these commits for as long as they stick around in each Git repository. How long that is depends on a number of factors. A standard "ordinary user" Git repository keeps these commits for at least 30 days by default. But if you don't know their hash IDs, how will you find these commits? The name branchA won't let you find them, because the way Git works, it turns a branch name into the hash ID of the last commit on the branch, and then works backwards from there. The commit that comes before J' is G. It's not the hash ID of any of the now-invisible commits.

This is what you and your co-worker are doing. I do not know why you are doing it. If you both like it, you can keep doing it. It's more difficult and error-prone than just adding new commits, and it's easy to completely lose a commit, if you don't know all the secret ways of finding lost commits. I don't recommend working this way—but if you want to, now you know what you are doing.

torek
  • 448,244
  • 59
  • 642
  • 775