3

I think what I am looking for is the oldest shared ancestor of two branches, or something like that, this question seems to touch on it: Finding a branch point with Git?

But instead of the diagram in the OP, this is more what I am looking at:

-- I -- I -- I -- I -- I -- I -- I  (integration branch) 
          \         \          /
           \         \        /
             F -- F -- F -- F  (feature branch)

My question is - if we checkout a feature branch from integration and make some changes and some commits, and then we update/merge with integration a few times as we go. Aka, commit commit, merge with integration, commit commit, merge with integration, etc. If we then do a git reset --soft <integration>, is that going to reset it to the commit on integration when git checkout was used, or will it simply reset it to the point where the last git merge with integration occurred?

The goal is so that I can make my feature into one big commit. If the git reset --soft only goes back as far as the last git merge with integration, then my feature might have 100s of commits, which is no bueno, and I will need another technique.

  • 1
    [`git reset --soft`](https://git-scm.com/docs/git-reset#git-reset---soft) moves the current branch where you tell it to move it. It does not do any search or decision by itself. You are looking for [`git merge-base --fork-point`](https://git-scm.com/docs/git-merge-base#git-merge-base---fork-point). – axiac Sep 02 '18 at 08:04

2 Answers2

1

You can use git reset --soft, but there's something else you must do—or rather, not do—first.

The goal is so that I can make my feature into one big commit.

In that case, make sure you don't start with:

-- o -- A -- B -- C -- D -- E -- IM   <-- integration
          \         \          /
           \         \        /
            F1 -- F2 - FM - F4   <-- feature

Note that I have replaced the individual letters here so that I can talk about particular commits. The two most interesting commits, as far as Git itself is concerned, are F4, which is the tip commit of the branch named feature, and IM, which is the tip commit of the branch named integration.

The commit labeled FM is not a problem, even though it is a merge commit. The commit labeled IM is a problem. That's because this commit is reachable from the tip of integration. For (much) more on the concept of reachability in general, see Think Like (a) Git. Whenever commit IM itself is reachable by starting at the commit to which integration points, and working backwards (leftwards), so are all commits that commit IM itself reaches. Commit IM leads back to both commit E (not a problem) and F4 (problem!).

If you eliminate commit IM, so that you have:

-- o -- A -- B -- C -- D -- E   <-- integration
          \         \
           \         \
            F1 -- F2 - FM - F4   <-- feature

you can now git checkout feature and run git reset --soft integration, resulting in this:

-- o -- A -- B -- C -- D -- E   <-- feature (HEAD), integration
          \         \
           \         \
            F1 -- F2 - FM - F4   [abandoned, but remembered as feature@{1}]

Your index and work-tree are unchanged from when you had commit F4 checked out, though, so you can now run git commit to turn the current index into a new commit. The name feature will now point to the new commit:

                              F   <-- feature (HEAD)
                             /
-- o -- A -- B -- C -- D -- E   <-- integration
          \         \
           \         \
            F1 -- F2 - FM - F4   [feature@{1}]

The snapshot for new commit F will match that for commit F4:

git diff feature@{1} feature

will print nothing at all. But the parent of new commit F is existing commit E. (Commit F also has a new author and committer and corresponding "now" time stamps, which also distinguish it from F4.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks for this - I will have to read about reachability, but I guess on the face of it I don't quite see why commit `IM` is a problem? –  Sep 02 '18 at 02:30
  • If you add a new commit atop commit `IM`, the new commit will point back to `IM`, so that `git log` will first show commit `F`, then `IM`, then—in some order—all of `A-B-C-D` and `F1-F2-FM-F4`. The reason is that Git traverses *both* chains (in backwards order, but otherwise simultaneously). – torek Sep 02 '18 at 05:41
0

Each time you are merging feature back to integration, you are moving integration HEAD (which now reference the new merge commit)

You need to mark integration before doing your feature branch and your merges in order to get back to it.

One possible marker would be origin/integration: that last time you fetch integration).

Another one is git merge-base --fork-point (based on reflog, so unreliable) or some diff between git rev-list --first-parent of both branches.

In any case, your git reset --soft would have to use that marker, not the local integration branch.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • yeah that makes sense, I guess `git reset --soft `, finds in the current branch and undos all commits back until that point. So it's just a matter of finding the right to go back to. –  Sep 02 '18 at 02:18
  • @OlegzandrDenman Yes, that is the idea. `git reset --soft` removes commits, but keep the final content intact (work tree and index). But you still need *where* to go back to. – VonC Sep 02 '18 at 02:20
  • Hmm yeah, isn't there some way to find out the first commit on a new branch? I just need to rewind back to that basically. Integration branch could get rebased or messed up, so I could potentially handle that by only looking at my feature branch and nothing else. –  Sep 02 '18 at 02:20
  • 1
    @OlegzandrDenman the only ways I know of are listed in the post you reference in your question, and that I allude to in my answer: merge-base of rev-list. – VonC Sep 02 '18 at 02:22
  • Yeah I googled some more and all signs point to those two, thanks –  Sep 02 '18 at 02:23
  • I think using `merge-base --fork-point` should work and looking at the git docs I don't think it *only* depends on the reflog, it uses other info too or something. But if you could explain how diffing `rev-list --first-parent` is supposed to work, that would be nice because that's less intutive. –  Sep 02 '18 at 02:36
  • @OlegzandrDenman It just looks for the first common commit between two histories. The --first-parent makes sure you are only selecting commits from the current branch, not commits from a merge branch (second parent) – VonC Sep 02 '18 at 02:37
  • I wonder if `git merge-base --fork-point` just does a diff on `git rev-list --first-parent` internally. –  Sep 03 '18 at 21:40
  • 2
    @OlegzandrDenman Not exactly: see https://stackoverflow.com/a/20966716/6309. Wait... this answer is not selected? – VonC Sep 03 '18 at 21:42
  • i didn't know whether to give you credit or @torek, I just flipped a coin TBH, I can reflip the coin as needed lulz –  Sep 03 '18 at 21:47
  • I will contact the StackOverflow mafia to get at least one more upvote for this answer –  Sep 03 '18 at 21:48
  • 1
    @OlegzandrDenman Which ever answer seems clearer to you is the right one. I just forgot about this question, and didn't realized that since I answered it, torek came along. – VonC Sep 03 '18 at 21:48