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
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.
- TFS Structure - Multiple Projects or Single Project?
- How do I convert simple non source controlled project backups into a versioned git repository?
- Reading a Git repository, without Git
- In git, what is the difference between a commit(s) and a revision(s)
- How am I supposed to manage db revisions alongside codebase revisions?
- How to delete a blob from git repo
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
D, as well as the entire branch, is public – so we can’t really inject commit
C. It’s okay if those commits (
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
Is there a better way than stashing, checking out
HEAD, then popping and committing?
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
- 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
- You have
masterchecked 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
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
git checkout master git cherry-pick <SHA1 of E>
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 (
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
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
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.