0

I'm actually learning Git and I'm wondering what would be the best practice to achieve the "Desired state" :

Actual state:

Master A-----B------C
             \ 
              \
           Dev D------E------F


Desired state:
Master
A-----B------C
             |
             |
Dev          C------D------E------F

FYI : I'm in production on Master/Commit-B and I'm developping on Dev/Commit-D-E-F. I've seen that I want to commit a change on production but Dev/Commit-D-E-F are not ready to be merged in production.

I've read about the usefull stackoverflow post : How to merge a specific commit in Git using git cherry-pick but I'm not too sure if i should be concern about "This changing of commit IDs breaks git's merging functionality among other things"? Should I need to change my commit vision to do not reproduce this scenario?

When my developpement will be finished I'm planning to merge the modification from Dev to Master as :

Future desired state:
Master
A-----B------C------D------E------F
Alexandre Roux
  • 283
  • 1
  • 4
  • 13
  • You said: *"FYI : The C' commit has not impact on D, E, F."* Well, that's fine, but what *is* the `C'` commit? It appears out of nowhere in your desired state, with no explanation... – Mark Adelsberger Aug 10 '17 at 16:30
  • Possibly related point: your diagram notation suggests a misconception about branches. Be aware that a branch is just a slightly-special ref (slightly special in that it moves in a certain way); a ref is just a pointer to a commit. The only commit with a strong relationship to a branch is the current commit for that branch (the one the ref directly points to). So treating rows of your diagram as representing a branch (implying that the commits shown on that row are related to that branch) is misleading. – Mark Adelsberger Aug 10 '17 at 16:34
  • @MarkAdelsberger "what is the C' commit?" - This was a misunderstanding from my side. I just corrected the diagram. – Alexandre Roux Aug 10 '17 at 16:43
  • @MarkAdelsberger I'm not too sure to understand what you are meaning on your second comment (also because I'm learning Git). This is what I've in my head, I'm in production on Master/Commit-B and I'm developping on Dev/Commit-D-E-F. I've seen that I want to commit a change on production but Dev/Commit-D-E-F are not ready to be merged in production. This is the point of my diagram. – Alexandre Roux Aug 10 '17 at 16:45

2 Answers2

3

As noted in comments, it's important to understand the loose relationship between commits and branches. A commit is either reachable from a branch, or not; it may be reachable from many branches. The fact that it was created while a particular branch was checked out, does not give it any special relationship with that branch.

What you've indicated is that you want the changes you made in C to also be applied to the dev branch. There are several ways to do this. First let's re-draw your "current state" in a notation that better reflects git concepts

A-----B------C <--(master)
       \ 
        \
         D------E------F <--(dev)

Here we see that master is a ref which is currently pointing to C. A and B may have originally been created on master, and certainly are reachable from master (by commit parent pointers); but their relationship with master is no stronger than that. In fact they are also reachable from dev and have exactly the same relationship with dev that they have with master in the current state; which is one way of looking at why dev reflects what you did in creating those commits.

Anyway, dev is a ref pointing to F, from which A, B, D, and E are also reachable.

Now there are a few additional problems with how your "desired state" is drawn; I'll draw up a few options for what you could do, and hopefully the concepts will be clearer.

So one option is to rebase dev so that it includes C. Technically this means that you create new commits (D', E', and F') that are the result of "replaying" the changes from D, E, and F onto C.

git rebase master dev

would give you

A-----B------C <--(master)
              \ 
               \
                D'------E'------F' <--(dev)

Note that with this option, you don't need C' because you're just making C "reachable" from dev. It is impossible to change D's parent, though, so instead you create D' (and then this in turn forces you to create E' in place of E, etc.)

Of note, D' and E' are untested states of the code. (So is F' but you probably would be testing that anyway.) You may or may not care to go back and test these generated intermediate snapshots, but if you don't it might interfere with future efforts to track down a bug (e.g. using bisect). If D' and E' aren't important enough to test, they probably aren't important enough to keep; so in that case you should consider doing the rebase interactively and squashing commits E and F, giving

A-----B------C <--(master)
              \ 
               \
                DEF <--(dev)

(where DEF is a single commit that results from replaying changes from D, E, and F).

When you want to incorporate dev into master, assuming no further changes occur to master in between, you can choose to "fast forward" master; in fact that's the default if you say

git checkout master
git merge dev

from this situation. This would give you

A-----B------C------D'------E'------F' <--(dev)(master)

(or

A-----B------C------DEF <--(dev)(master)

if you had used interactive rebase to squash the dev commits). This is a result many people like for its simple linearity. Another option would be

git checkout master
git merge --no-ff dev

which gives you

A-----B------C --------------------M <--(master)
              \                   /
               \                 /
                D'------E'------F' <--(dev)

where M will have the same TREE (content state) as F' (since master has no changes that weren't already accounted for in dev). (Again D'...F' would be replaced by DEF if you squashed during the original rebase.) This would be a little unusual, in that the rebase is usually used to set up a fast-forward so you'd have a linear history. If you want to preserve the branch topology (which is why you'd use --no-ff), you might choose a different means of reflecting C's changes in dev in the first place.

So going back to your current state, instead of rebasing you could merge from master to dev

git checkout dev
git merge master

This would give you

A-----B--------------------C <--(master)
       \                    \
        \                    \
         D------E------F------M <--(dev)

Again this makes C changes reachable in dev without duplicating the commit. In fact you don't duplicate or replace any existing commits this way, which is especially nice if the existing commits have been shared with other developers (e.g. pushed to origin).

But here you create a merge commit (M), and some people consider these "backwards merge" commits to be ugly clutter. It's a somewhat understandable criticism; suppose you did this, then tested the merged result, then immediately merged back to master; you'd either allow a fast-forward and get get

A-----B--------------------C
       \                    \
        \                    \
         D------E------F------M <--(dev)(master)

which looks ok, but the parents of M are not listed in the usually-expected order which may confuse people using --first-parent to navigate the history...

or forbid fast-forward and get

A-----B--------------------C-----M2 <--(master)
       \                    \   /
        \                    \ /
         D------E------F------M <--(dev)

which does seem a little weird.

One workflow that avoids these results is to consider the "backwards merge" temporary (for testing only) and undo it once testing is done.

git checkout dev
git reset --hard HEAD^

That again means that in the end you're storing probably-untested states of the code. Also, if M had any conflicts, then you lose the resolution of those conflicts by doing this; and if dev isn't immediately ready for re-integration into master, you could end up re-doing that resolution repeatedly. git rerere is available to mitigate this issue.

fwiw, my preference is to just accept the merge commit "clutter" as it generally doesn't hurt anything (and git can help you filter it out of log output anyway).

Anyway, you see that we still haven't had to create a C' (because C doesn't "belong to master" in any way that prevents us incorporating it directly into dev). Replaying the changes in C to a new C' commit "on dev" is an option, but I recommend against it, because duplicate commits just create the risk of unnecessary merge conflicts down the line. If you really want to do it this way, you could use cherry-picking or, in the specific case you site, a so-called "squash merge" of master into dev. (There are other ways as well.)

Mark Adelsberger
  • 32,904
  • 2
  • 24
  • 41
  • Thanks for the details and all explanations, it's really an excellent answer! I agree and understand to all first half of your answer but _"So going back to your current state, instead of rebasing you could merge from master to dev"_ does not make sense for me as why should I merge the two branches if the code development (commit D+E+F) in dev is not ready to be integrate in master (and in production code). I guess here you reference the moment **where the code from dev will be ready to be merge on master** (and run on production environnement) correct? – Alexandre Roux Aug 10 '17 at 18:30
  • That's the difference between "merging dev into master" vs. "merging master into dev". Note in the diagram, that when merging `master` into `dev`, `master` ref doesn't move and so the changes from `dev` are *not* included in `master`. (Conversely, when you *do* finally merge `dev` into `master`, the `dev` branch is unchanged.) So: you don't "merge two branches together" - you merge one branch *into* another branch, and only the latter is affected. (Technically you can merge in things other than branches, or merge multiple things in; but those are both less common.) – Mark Adelsberger Aug 10 '17 at 19:05
2

The first one looks like you're looking for git rebase. When on Dev: git rebase master.

Master A-----B------C
             \ 
              \
           Dev D------E------F

After Rebase

Master A-----B------C
                     \ 
                      \
                      Dev D------E------F

However, I don't get your second graph. What is the idea of developing D, E and F and then just applying F to the master? Nevertheless, for that case, as you already mentioned, git cherry-pick seems appropriate (git cherry-pick F). Normally, git cherry-pick does not cause any problems (at least in the "standard" cases I used it for).

SVSchmidt
  • 5,336
  • 2
  • 17
  • 33
  • "What is the idea of developing D, E and F and then just applying F to the master?" - This is a missunderstanding on my side, I want of course merge all commit from Dev to Master at the end. – Alexandre Roux Aug 10 '17 at 16:35
  • 1
    "I want of course merge all commit from Dev to Master at the end." - In this case, would simply merging dev onto master accomplish what you want? The default behaviour here is for the new commit * to contain all the changes made on the Dev branch since it diverged from master. Master A-----B------C----------------- * \ / \ / Dev D------E------F – Matt Slonetsky Aug 10 '17 at 16:45
  • @AlexandreRoux Okay, as of your edits a simple `git merge Dev` will be sufficient later. However, as you see from my graph, a `rebase` will change the basis for Dev to the C commit of the Master (hence, its changes are available in Dev). This is useful when you want the branches not to diverge too much and want to avoid merge commits (rebase ensures a clean, linear history). I would always prefer rebasing the feature branches with the master before merging back because of that. – SVSchmidt Aug 10 '17 at 16:48
  • @MattSlonetsky "In this case, would simply merging dev onto master accomplish what you want?" - Yes it will at the end I guess, but at the moment I'm in this scenario : I'm in production on Master/Commit-B and I'm developping on Dev/Commit-D-E-F. I've seen that I want to commit a change on production but Dev/Commit-D-E-F are not ready to be merged in production. – Alexandre Roux Aug 10 '17 at 16:53
  • @MattSlonetsky i think `rebase` is what I'm looking for here according to your diagram. I'm going to give a try :). – Alexandre Roux Aug 10 '17 at 16:54