Tutorial 6 of 8
Tags and Cherry-Pick
Mark a release with a tag, and copy just the commits you need from another branch.
About 5 minutes
What you will be able to do
- Create a lightweight tag such as v1.0, and know when to use an annotated tag instead.
- Push tags on purpose, because git push does not send them by default.
- Cherry-pick one commit or a range from source-branch, and use A^..B to include the first commit.
- Explain why cherry-picked commits get new IDs.
Title
Tags and Cherry-Pick. Name a moment, copy a change.
This tutorial covers tags, which name important commits, and cherry-pick, which copies chosen commits to your branch.
Analogy: a name sticker that never moves
A tag is a fixed name on one commit, like v1.0. Unlike a branch, it stays put when you commit.
A tag permanently names one commit, so you can always find a release such as v1.0 later.
A tag is a name sticker on one photo. The sticker stays on that photo even as the album grows.
A sticker is only a name, and a lightweight tag is the same, with no message, author or date of its own, so releases should use annotated tags.
Create a lightweight tag
git tag v1.0 tags the commit you are on. Add a commit ID to tag an older commit.
git tag v1.0 # tags the current commit (HEAD) git tag v0.9 4e5f6a7 # tags an older commit git tag # lists all tags
Without extra options, git tag creates a lightweight tag, which is simply a name that points at a commit.
Annotated tags for releases
For releases, use an annotated tag. It stores the tagger, the date and a message.
git tag -a v1.1 -m "Release 1.1: dark mode" git show v1.1
Annotated tags are full objects with their own author details and message, which is why Git recommends them for releases.
Tags do not travel by default
git push does not send tags. Push one tag by name, or all tags with --tags.
git push origin main # commits only, no tags git push origin v1.0 # one tag git push origin --tags # every tag
Tags stay on your laptop until you push them yourself, either one by name or all together.
Analogy: photocopying a recipe card
git cherry-pick copies the change from one commit and adds it as a new commit on your branch.
Cherry-pick applies the same change on your branch as a new commit, while the original stays on its own branch.
Cherry-picking is like photocopying one recipe card from a friend's box and filing the copy in your own box.
A photocopy is an exact copy, but the new commit has a different parent and commit time, so it gets a new ID and Git treats it as a separate commit.
Pick one commit
Find the commit ID on the other branch, switch to your branch, then cherry-pick it.
git log --oneline source-branch # 9d8e7f6 Add dark mode # 8b7c6d5 Fix footer link # 1a2b3c4 Add footer git switch main git cherry-pick 8b7c6d5
You name the commit to copy by its ID, and Git applies its change to the branch you are on.
Ranges: A..B leaves out A
A range A..B picks the commits after A, up to and including B. A itself is not copied.
git cherry-pick S1..S3
In the range A..B, Git copies what is reachable from B but not from A, so A is left out and the copies are applied oldest first.
Include A with A^..B
To include A, start from its parent: A^..B. Now A, B and everything between are copied.
git cherry-pick S1^..S3
The caret means "parent of", so starting the range at A's parent makes A part of the copy.
Conflicts and good habits
Cherry-pick a few specific fixes, not whole branches. On a conflict: fix, git add, then git cherry-pick --continue.
git cherry-pick --continue # after fixing and git add git cherry-pick --abort # give up and go back git cherry-pick -x 8b7c6d5 # note the source ID in the message
Cherry-pick is best for a small number of commits, and it stops for conflicts the same way a merge does.
Recap
Tags stay put. Annotated tags for releases. Push tags yourself. Cherry-picks are copies with new IDs. Use A^..B to include A.
{"commits": [{"id": "C1", "parents": []}, {"id": "C2", "parents": ["C1"]}, {"id": "C3", "parents": ["C2"]}, {"id": "C4", "parents": ["C3"]}, {"id": "C5", "parents": ["C4"]}], "refs": [{"name": "main", "target": "C5", "kind": "branch"}, {"name": "v1.0", "target": "C2", "kind": "tag"}], "head": {"ref": "main"}}You can now name releases and copy chosen commits, and next you will learn to read and fix history.
Check yourself
0 of 5 answered
Check 1 of 5
Click the commit that v1.0 points to now.
main has C1, C2, C3, C4 and C5. You created the lightweight tag v1.0 while HEAD was on C2, then made three more commits.
Check 2 of 5
You tagged v1.0 and ran git push origin main. Is v1.0 on the server now?
Check 3 of 5
On main you run git cherry-pick 5a6b7c8..9e1f2a3. Which commits are copied?
$ git log --oneline source-branch 9e1f2a3 Add dark mode 7c8d9e0 Fix footer 5a6b7c8 Add footer 3f4a5b6 Start page
Check 4 of 5
Why does a cherry-picked commit usually have a different ID from the original?
Check 5 of 5
Which should you use to mark a public release?
mkdir -p ~/git-practice/t06-tags && cd ~/git-practice/t06-tags git init -b main echo "v1" > app.txt && git add app.txt && git commit -m "Start app" git tag v1.0 # A source branch with four fixes. The tags fix1 and fix3 mark two of them. git switch -c source-branch echo "fix 1" > fix1.txt && git add fix1.txt && git commit -m "Fix 1" && git tag fix1 echo "fix 2" > fix2.txt && git add fix2.txt && git commit -m "Fix 2" echo "fix 3" > fix3.txt && git add fix3.txt && git commit -m "Fix 3" && git tag fix3 echo "fix 4" > fix4.txt && git add fix4.txt && git commit -m "Fix 4" # Range without the first commit git switch main git cherry-pick fix1..fix3 # copies Fix 2 and Fix 3 only git log --oneline # Range including the first commit git switch -c try-inclusive v1.0 echo "Readme" > readme.txt && git add readme.txt && git commit -m "Add readme" git cherry-pick fix1^..fix3 # copies Fix 1, Fix 2 and Fix 3 git log --oneline git log --oneline source-branch # compare: the copies have different IDs # An annotated tag for a release git switch main git tag -a v1.1 -m "Release 1.1" git show v1.1 # press q to leave the pager git tag # Optional: see that tags are not pushed by default git init --bare -b main ../t06-server.git git remote add origin ../t06-server.git git push origin main git ls-remote --tags origin # prints nothing yet git push origin v1.0 git ls-remote --tags origin # now v1.0 is on the "server"