Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git reset behavior

Tags:

git

merge

rebase

I have the following situation:

The master branch had a stable version of application.

Developer A recently made a feature-branch called branch-a with several commits (let them be a-1, a-2, a-3). Implemented features here are based on up-to-date code from master and are well-tested for the moment.

Developer B had a feature-branch called branch-b with several commits (for example, b-1, b-2, b-3). For some reason Mr. B had an outdated version (based on master's state of a week or two ago) in his feature-branch and didn't tested the code at all.

Both developers merged their feature-branches to master using:

  1. git checkout master
  2. git pull origin master
  3. git merge branch-X (where X = a,b)
  4. git push origin master

No rebase command was used. First this sequence was done by B, next - by A.

When I (developer C) pulled from master, I have seen in git log something like:

  • a-merge: merge with master by developer A
  • a-3
  • a-2
  • a-1
  • b-3 (yes, this commit comes right after one that merges)
  • b-merge-conflicts: merge with master by developer B (thousands of files conflicting)
  • b-2
  • b-1
  • master-stable: previous stable commits

As a result Mr. B somehow had forced the old version of the code to overwrite the stable version when merging (resulted in b-merge-conflicts commit).

NOW I want to rewrite the history and save b-1 + b-3 + a-1 + a-2 + a-3 changes and undo b-2, b-merge-conflicts and a-merge.

My idea is to undo several top commits till b-1 and then to use cherry-pick patching to apply b-3, a-1, a-2, a-3 commits to the new master.

BUT when I try:

git reset --hard HEAD~7 I can see a history containing only old commits (before master-stable) without those of branch-a and branch-b.

When I try:

git reset --hard HEAD~2

I can see in the history only master-stable commit at the top but NOT a-2 as I want.

Looks like git reset doesn't translate a digit after HEAD as a number of commits to reset (as I had undestood from the documentation), but as a number of HEAD-changes by git pull (in my examples there are 2).

How can I properly undo the first 7 commits b-2 .. a-merge and rewrite a history starting from b-1?

UPDATE asked in comments

I used (without --all to exclude additional info)

git log --oneline --decorate --graph

*   ef7d93f Merge with master by Developer A
|\
| * 2b9dd31 b-4
| * 924a452 b-3
| * 1f9489d b-2
| *   e3cd7a6 Merge by Developer B [2]: Merge branch 'master' from https://github.com ....
| |\
| * | aece506 Merge by Developer B [1]: merge branch
| * | 487e7ee b-1
* | | d9404f8 a-1
| |/
|/|
* | 9b202ce master-stable last commit
like image 628
Andrey Pesoshin Avatar asked Feb 21 '15 17:02

Andrey Pesoshin


2 Answers

git log is lying to you. It's presenting Git history as if it's linear, it's showing you the commits in date order. That isn't very useful. git log --graph --decorate will give you a clearer story by showing you the tree (graph really) of commits. From what I can work out, your repository looks like this.

                                       a1 - a2 - a3
                                      /            \
origin c1 - c2 - c3 - c4 - b-merge - b3 -------- a-merge [master]
        \                  /
         b1 -------------b2

As you can see, "go back seven commits" can have several interpretations. This is why you should avoid that notation for moving more than a few commits back, and instead refer to commit IDs.

What you want is this.

                  a1 - a2 - a3 [branch-a]
                 /
c1 - c2 - c3 - c4 [master]
                 \
                  b1 - b3 [branch-b]

To get there, create A and B branches off c4 so you have a place to build off of.

git branch branch-a c4
git branch branch-b c4

             [branch-b]         a1 - a2 - a3
             [branch-a]        /            \
c1 - c2 - c3 - c4 - b-merge - b3 -------- a-merge [master]
  \                  /
   b1 -------------b2

Now checkout those branches and cherry pick the appropriate changes onto them, fixing any conflicts.

                  b1b - b3b [branch B]
                 / 
                |   a1a - a2a - a3a [branch A]
                |  /  
                | /                a1 - a2 - a3
                |/               /            \
c1 - c2 - c3 - c4 - b-merge - b3 -------- a-merge [master]
  \                  /
   b1 -------------b2

That might look like a mess, but now checkout master and git reset --hard c4 and all the mess kept alive by master being at a-merge will drop away (that's a white lie, origin/master will keep it visible until you push, also Git won't actually throw the commits out for weeks).

                  b1b - b3b [branch B]
                / 
               |  a1a - a2a - a3a [branch A]
               |/
c1 - c2 - c3 - c4 [master]

Now you can merge A and B normally. When you're done, you'll have to push --force because master is not a child of origin/master.

This is just one way to accomplish what you want. The important thing is to be able to visualize the repository graph, where you want it to be, and what commands will transform it.

like image 177
Schwern Avatar answered Sep 18 '22 07:09

Schwern


I am not sure about HEAD~ because I usually use HEAD^.

You don't need to use that notation though. You can just supply the hex SHA-1 hash of the commit, or the first 7 or so digits of it.

git reset --hard 72abfd4
like image 43
David Grayson Avatar answered Sep 20 '22 07:09

David Grayson