Home > OS >  Prevent Git Fast Forward Automatic Merge
Prevent Git Fast Forward Automatic Merge

Time:10-19

Questions context example:

  1. A branch B was created from A branch
  2. In branch B a file called XPTO.txt was created and edited several times (several commits)
  3. Another branch C was created from branch B afterwards
  4. In parallel XPTO.txt was edited several times (several commits) again in branches B and C
  5. The branch C must to be merged back to A
  6. The pull request (PR) from branch C into branch A must not have the changes made in XPTO.txt from branch B before branch C was created

Assuming theses changes (commits) removing the B branch code are done properly and are not affecting the project build and etc.

How should I properly manage the repository in order to:

  • Merge the branch B code removal commits in branch A
  • Avoid (as much as possible) merge conflicts when branch B open a PR in order to merge into branch A
  • Most importante: implicit avoid, if possible, Git fast forward that might happen

Scale the example to several files and several changes (commits).

The only answer for this that I figured so far is force a non FF merge from branch B into branch A but still getting Already up to date! sometimes.

Thanks.

CodePudding user response:

Given these steps:

  1. A branch B was created from A branch
  2. In branch B a file called XPTO.txt was created and edited several times (several commits)
  3. Another branch C was created from branch B afterwards
  4. In parallel XPTO.txt was edited several times (several commits) again in branches B and C

Your repository looks something like this:

                               (B)
                                |
                                v
                      <--b3 <--b4
                    /
a1 <--a2 <--b1 <--b2
       ^            \
       |             <--c1 <--c2
      (A)                      ^
                               |
                              (C)

The lower-case labels are individual commits, linked together by their "parent" references (the backwards-facing arrows). The upper-case labels are branches, which in git's model are just a pointer to a particular commit, which can be used to reference that commit plus all its ancestors.

Note that the commits b1 and b2 are the ones originally created on branch B, but as far as git is concerned they are just as much a part of the history of branch C.

So now:

  • The pull request (PR) from branch C into branch A must not have the changes made in XPTO.txt from branch B before branch C was created

There is no direct way of telling git this - it doesn't know which commits "belong to" branch B or "came before" branch C. If you ask to merge branch C into A, it will look back until it finds a common ancestor, which as a2, so the commits to merge are b1, b2, c1, and c2.

In order to "remove" those commits, you need to create new commits that don't have them in their history. This is what the "git rebase" command is for.

In this case, you need to rebase the commits after "b2" onto "A", so the command would be git rebase b2 C --onto A. The result will look something like this:

                               (B)
                                |
                                v
                      <--b3 <--b4
                    /
a1 <--a2 <--b1 <--b2
      ^ \           \
      |  \           <--c1 <--c2
     (A)  \
           <--c3 <--c4
                     ^
                     |
                    (C)

Now commits b1 and b2 aren't part of C's history any more.

Commits c3 and c4 will be created by the rebase command based on c1 and c2, respectively, but don't link to them in any way. If there's no other branch or tag pointing at commits c1 and c2, they will eventually be "garbage collected" as orphaned data.


If you want to have part of the changes from commits b1 and b2, you will need to add them back manually as a new commit. This may or may not lead to conflicts later, depending on whether the merge algorithm can figure out what you're trying to do. But that's just a fact of life: two parallel changes to the same file have a risk of conflicting.


Note that this is very different from reverting the changes (either with "git revert", or manually undoing them) which creates additional commits in the history:

                               (B)
                                |
                                v
                      <--b3 <--b4
                    /
a1 <--a2 <--b1 <--b2
       ^            \
       |             <--c1 <--c2 <--rb1 <--rb2
      (A)                                   ^
                                            |
                                           (C)

Here, "rb1" undoes the changes from "b1", and "rb2" undoes the changes from "b2", but all four commits are part of C's history. Once you merge to A, they will all be part of A's history as well, so only b3 and b4 will be "new" when you merge in branch B.

The only other way around this is to rebase branch B to create new copies of the commits to merge after the revert. This leads to a messy history, but is sometimes the way out of a mess.

                               (B)
                                |
                                V
          <--b5 <--b6 <--b7 <--b8
        /
       |
       |             <--b3 <--b4
       |            /
a1 <--a2 <--b1 <--b2
       ^            \
       |             <--c1 <--c2 <--rb1 <--rb2
      (A)                                   ^
                                            |
                                           (C)

Here, b5, b6, b7, and b8 are the versions of b1, b2, b3, and b4 recreated by the rebase command.

  • Related