Create tag and make local changes, then rebase against public branch

We have a situation in which (for this example) we have a straight line of development that is preparing for a release:

A --> B --> C --> D
                  ^ HEAD

We decide that commits C and D are too forward-thinking, and we want to do the release from B. We have a tool that will go back and tag B as our release version, say product-1.0.0, and will then update a number of versioning files to 1.1.0, or whatever version is next.

The problem is the creation of the tag, and the tweaking of the files, is an atomic operation. We can change this, but it’s a little bit more hassle.

So while the tag needs to point at B, we want our file changes to be popped into a new commit. What we currently have:

      v detached, tagged, with local changes
      |
A --> B --> C --> D
                  ^ HEAD

But, if we commit from our current point, we end up with:

       /--> E (a 'lost' commit)
      /
A --> B --> C --> D
      ^ tag       ^ HEAD

What is the easiest way, either from the “detached with local changes” point, or from the “lost commit” point, to merge either our local changes, or our new commit, to the public branch so that it looks like:

A --> B --> C --> D --> E
      ^ tag             ^ NEW HEAD

The commits C and D, as well as the entire branch, is public – so we can’t really inject commit E between B and C. It’s okay if those commits (C and D) exist as ‘newer’ 1.0.0 versions of the product we’re releasing, we just want to create a new commit on top of the current HEAD.

Is there a better way than stashing, checking out HEAD, then popping and committing?

  • Can reordering commits with git interactive rebase cause a conflict?
  • Git rebase failing
  • How to see all tags in a git repository in command line
  • For a .Net project, what file extensions should I exclude from source control?
  • Adding files to git index without committing
  • SVN Source control issues when merging changes
  • Building Subversion 1.5.4 on Debian: could not find library containing RSA_new
  • Are there issues having this feature workflow with Git?
  • 2 Solutions collect form web for “Create tag and make local changes, then rebase against public branch”

    This answer show a workflow which uses local branches as an alternative to the stash mechanism. This is the approach I prefer myself.

    I’m going to assume that:

    • Your public branch head is called master
    • This “atomic operation” is a script used for making releases.
    • The release tag (product-1.0.0) needs to point to B in the end.
    • The release commit (E) needs to be “added” to the tip of master, after C and D.
    • You have master checked out and your working tree is clean.

    The history looks like this initially (the origin remote represents what’s public, HEAD points to your currently checked out branch head):

    (A)<--(B)<--(C)<--(D)
                      ^ ^
                     /   \
               master     orgin/master
                    ^
                    |
                 HEAD
    

    You need to be able to see this graph yourself. I usually use a git alias like this (g for “graph”):

    git config --global alias.g "log --decorate --oneline --graph"
    

    You can then get the picture above by invoking:

    git g master origin/master
    

    (That displays everything reachable from master and/or origin/master. Use git g --all to show everytihng.)

    First create a local temporary branch head called temp that points to B, and then check it out.

    git branch temp <SHA1 of B>
    git checkout temp
    

    The commit graph now looks like this:

           HEAD
           |
           v
           temp
           |
           v
    (A)<--(B)<--(C)<--(D)
                      ^ ^
                     /   \
               master     orgin/master
    

    Run your release script which tags the current commit, makes a change (probably bumping up the release number or something), and then makes a commit of that change.

    <magic release script>
    

    If I understand your release script correctly, then the resulting graph should look like this:

                            HEAD
                            |
                            v
                            temp
                            |
                            v
     tag: product-1.0.0    (E)
                       \   /
                        v v
                  (A)<--(B)<--(C)<--(D)
                                    ^ ^
                                   /   \
                             master     orgin/master
    

    It’s now time to move back to master and to “carry over” the E commit:

    git checkout master
    git cherry-pick <SHA1 of E>
    

    The cherry-pick command treats a single commit as a patch. The “patch” contains the difference between the tree snapshot at B and the snapshot at E. This “patch” is then applied at whatever branch head you have checked out – in this case master. The changes in the patch are made into a new commit (F) with the same message, and author as the original one (E).

                            temp
                            |
                            v
     tag: product-1.0.0    (E)
                       \   /
                        v v
                  (A)<--(B)<--(C)<--(D)<--(F)
                                     ^     ^
                                     |     |
                         origin/master     master
                                           ^
                                           |
                                           HEAD
    

    Note that the F commit is not identical to the E commit! It is only “the same in spirit”, that is it introduces the same logical change.

    After this, review the final state and you should be ready to push the new master state to origin to make it public.

    git push origin master:master
    

    You can now remove the temporary temp branch head:

    git branch -D temp
    

    You usually use the -d option to delete a branch, but git won’t let you use it here. The reason is that you will lose the E commit. In this case it is OK since we have “saved” E by using cherry-pick. As git considers E and F to be distinct commits it can’t know that the “spirit of E” remains in F and no information would be lost. You need to tell git that you have made sure that you know what you are doing by using -D instead of -d.

    Finally you should have:

        tag: product-1.0.0
                         |
                         v
                  (A)<--(B)<--(C)<--(D)<--(F)
                                          ^ ^
                                         /   \
                            origin/master     master
                                              ^
                                              |
                                              HEAD
    

    Branch at your tag and put E on that branch and merge the branch_E back into you main branch or just cherry-pick commit E back on the main branch after D.

    Git Baby is a git and github fan, let's start git clone.