Release branches and tags
- Lesson 13
- 30 min
- Module 5: Release and hotfix practice
Lesson 13 of 15 · Git and Team Development EssentialsRelease branches and tags
1. Understand a repository
2. Work with branches and remotes
3. Collaborate through reviews
4. Resolve and recover
5. Release and hotfix practice
What you will be able to do
Give the help desk a version of the notes that does not change while your team continues to work. After this lesson, you can say which reference holds new work, which holds the next release, and which holds the released version.
Before you startWhat to know and to have ready
What you need to know
- How to merge a pull request and read the merge commit in
git log --graph, from Pull-request sequence: 8. Merge. - How to push a branch and set its upstream, from Synchronize deliberately: Amira pushes.
What you need on your computer
- The folder
~/projects/nextia-learning-labs. You cloned it in Pull-request sequence: Set up the practice lab. If it is missing, do step 2 of that section. - Git, with your name and email address, from Set up Git: Step 2: tell Git who you are. Git writes them in each tag that you make.
- This lesson uses a new practice state, called
release. You make it in Set up the practice state. The setup command deletes your oldc14-labfolder and makes it again. You do not need your work from Module 4. - The hashes of the commits that the setup script makes are the same as in this lesson. Commits that you make yourself have different hashes.
The team releases a version
Section titled “The team releases a version”The team writes help desk notes in ticket-notes. Until now, all work went onto main. Now the help desk wants a fixed version that it can use every day. The help desk calls it version 1.0.
main cannot be that version. The team commits to main every day, so main changes every day. The help desk needs notes that change only when the team decides to fix something.
So the team uses three references. A reference is a name that points to a commit, for example a branch name.
| Reference | Example | What it holds | Does it move? |
|---|---|---|---|
| Development | main |
New work. All finished changes go here. | Yes, with each merged change. |
| Release candidate | release/1.0 |
The next release. The team tests it. | Only for fixes. |
| Production | v1.0.0 |
The version that the help desk uses. | Never. |
A release branch is a branch that you make from main when the next version is ready. It is the release candidate: the version that the team tests before it gives the version to the help desk. After you make it, it accepts fixes only. New work continues on main, and goes into the next release.
A tag is a fixed name for one commit. A branch moves when you commit on it. A tag never moves. When the team releases version 1.0, it puts the tag v1.0.0 on the release commit. “Production” is the name for what the users have now, so the tag marks production.
Version numbers
Section titled “Version numbers”Most teams write versions as three numbers: MAJOR.MINOR.PATCH.
| Change | Example | When |
|---|---|---|
| PATCH | 1.0.0 → 1.0.1 |
A fix only. Nothing new. |
| MINOR | 1.0.1 → 1.1.0 |
New content that does not change how people use the notes. |
| MAJOR | 1.1.0 → 2.0.0 |
A change that people must learn again, for example a new file layout. |
The v at the start of v1.0.0 is a common convention for tag names. The release branch has only the first two numbers, release/1.0, because all 1.0.x fixes go onto it.
Set up the practice state
Section titled “Set up the practice state”-
Go to your projects folder, and run the setup script with the scenario
release:Terminal cd ~/projectssh nextia-learning-labs/C14/setup.sh release --resetOutput Ready: /Users/amira/projects/c14-labticket-notes your clonesam-notes Sam's cloneteam-remote.git the shared remoteNext: cd c14-lab/ticket-notesTo start this scenario again: sh nextia-learning-labs/C14/setup.sh release --reset -
Go into your clone, and look at the history:
Terminal cd c14-lab/ticket-notesgit log --oneline -3Output 465d7ea (HEAD -> main, origin/main, origin/HEAD) Add escalation rulesf330fdc Add ticket 104e058b3f Add ticket 103If your hashes are different, run the setup command again. Do not continue with different hashes.
Worked example: Amira releases version 1.0
Section titled “Worked example: Amira releases version 1.0”The team agrees that main at 465d7ea is ready to become version 1.0.
Make the release branch
Section titled “Make the release branch”Current branch: main. Amira runs the check first, because a release must pass it:
sh check.shPASS: 4 ticket lines checkedShe makes the release branch from main, and pushes it, so that the team can see it:
git switch -c release/1.0git push -u origin release/1.0Switched to a new branch 'release/1.0'To /Users/amira/projects/c14-lab/team-remote.git * [new branch] release/1.0 -> release/1.0branch 'release/1.0' set up to track 'origin/release/1.0'.Current branch: now release/1.0. The / in the name is only part of the name. Teams use release/ to keep all release branches together in lists.
Tag the release
Section titled “Tag the release”The team tested the candidate, and found no problem. Amira tags the commit as version 1.0.0. The -a option makes an annotated tag: a tag that also stores who made it, when, and a message.
git tag -a v1.0.0 -m "Help desk notes 1.0"git tagv1.0.0git tag with no name lists all tags. To see what a tag points to, use git show:
git show v1.0.0tag v1.0.0Tagger: Amira Khan <amira@example.com>Date: Mon Oct 5 22:03:44 2026 -0400
Help desk notes 1.0
commit 465d7ea589a41feac19db85754dd27d487d78f76Author: Amira Khan <amira@example.com>Date: Tue Oct 6 09:09:00 2026 -0400
Add escalation rules
diff --git a/escalation.txt b/escalation.txt...The first part is the tag: who made it, when, and the message. The second part is the commit that the tag points to, 465d7ea.
Push the tag
Section titled “Push the tag”git push does not send tags. Amira checks this:
git pushEverything up-to-dateThe tag is still only in her repository. She pushes it by name:
git push origin v1.0.0To /Users/amira/projects/c14-lab/team-remote.git * [new tag] v1.0.0 -> v1.0.0Now Sam can get it. In sam-notes, a fetch shows the new branch and the new tag:
git fetchgit tagFrom /Users/amira/projects/c14-lab/team-remote * [new branch] release/1.0 -> origin/release/1.0 * [new tag] v1.0.0 -> v1.0.0v1.0.0main continues, the release does not move
Section titled “main continues, the release does not move”Current branch: main. Amira switches back with git switch main. She starts a new feature, a ticket export. She makes export.txt:
Export format: one CSV row for each ticket.TODO: decide the columns.The export is not finished, but she commits and pushes her progress on main:
git add export.txtgit commit -m "Start the ticket export (not finished)"git push[main eaa41a2] Start the ticket export (not finished) 1 file changed, 2 insertions(+) create mode 100644 export.txtTo /Users/amira/projects/c14-lab/team-remote.git 465d7ea..eaa41a2 main -> mainNow she looks at all references. --decorate shows the branch and tag names next to the commits:
git log --oneline --graph --all --decorate* eaa41a2 (HEAD -> main, origin/main, origin/HEAD) Start the ticket export (not finished)* 465d7ea (tag: v1.0.0, origin/release/1.0, release/1.0) Add escalation rules* f330fdc Add ticket 104Read it from the top:
mainmoved forward toeaa41a2. Development continues.release/1.0stayed on465d7ea. The unfinished export is not in the release candidate.v1.0.0stayed on465d7ea. The help desk still has exactly version 1.0.
The release branch can still move: if the help desk finds a bug in 1.0, a fix goes onto release/1.0, and the team tags v1.0.1. The next lesson shows how. The tag v1.0.0 never moves. It always means the version that the help desk got first.
Guided practice: release version 1.0
Section titled “Guided practice: release version 1.0”-
Run the setup command from Set up the practice state, and go into
c14-lab/ticket-notes. -
Run
git status. The output must includeOn branch mainandworking tree clean. -
Run
sh check.sh. The output must start withPASS. -
Make the release branch, and push it:
Terminal git switch -c release/1.0git push -u origin release/1.0Current branch: now
release/1.0. The output includes* [new branch] release/1.0 -> release/1.0. -
Tag the release:
Terminal git tag -a v1.0.0 -m "Help desk notes 1.0" -
Run
git show v1.0.0. Check that the commit line shows465d7ea. -
Push the tag:
Terminal git push origin v1.0.0The output includes
* [new tag] v1.0.0 -> v1.0.0. -
Switch to
main:Terminal git switch main -
In your editor, make
export.txtwith the two lines from the worked example. Save it. -
Commit and push it on
main:Terminal git add export.txtgit commit -m "Start the ticket export (not finished)"git push -
Run
git log --oneline --graph --all --decorate. Check three things:mainis on your new commit.release/1.0is on465d7ea.tag: v1.0.0is on465d7ea.
-
Go to Sam’s clone, and check that he can see the tag:
Terminal cd ../sam-notesgit fetchgit tagThe output ends with
v1.0.0. Then go back withcd ../ticket-notes.
Your turn: release version 1.1
Section titled “Your turn: release version 1.1”The export is now finished. The team wants to release it as version 1.1.
- On
main, completeexport.txt. For example, replace theTODOline withColumns: number, summary, action.Commit and push. - Make the release candidate for 1.1, tag it as the released version 1.1.0, and push the branch and the tag. Choose the names yourself, with the rules from this lesson.
- Prove three things with commands:
release/1.0andv1.0.0did not move.- Version 1.0.0 does not contain
export.txt. - Version 1.1.0 contains
export.txt.
Show a model answer
The names are release/1.1 (a new MINOR version, with new content) and v1.1.0.
git switch maingit commit -am "Finish the ticket export"git pushgit switch -c release/1.1git tag -a v1.1.0 -m "Help desk notes 1.1"git push -u origin release/1.1git push origin v1.1.0git log --oneline --graph --all --decorate -5* 5cddf4e (HEAD -> release/1.1, tag: v1.1.0, origin/release/1.1, origin/main, origin/HEAD, main) Finish the ticket export* eaa41a2 Start the ticket export (not finished)* 465d7ea (tag: v1.0.0, origin/release/1.0, release/1.0) Add escalation rules* f330fdc Add ticket 104* e058b3f Add ticket 103release/1.0 and v1.0.0 are still on 465d7ea. To see the files in each version, list the files in the tag:
git ls-tree --name-only v1.0.0.gitignoreREADME.mdcheck.shcontacts.txtescalation.txttickets.txtexport.txt is not in the list. The same command with v1.1.0 lists export.txt too.
Your hashes for 5cddf4e and eaa41a2 are different, because you made those commits yourself.
Common mistake
I tagged, but Sam cannot see the tag
Symptom: you made v1.0.0 and ran git push. Sam runs git fetch, then git tag, and sees nothing:
From /Users/amira/projects/c14-lab/team-remote * [new branch] release/1.0 -> origin/release/1.0Cause: git push sends branches, not tags. The tag is only in your repository.
Fix:
-
Check which tags the remote has:
Terminal git ls-remote --tags originIf the output is empty, the remote has no tags.
-
Push the tag by name:
git push origin v1.0.0. -
Ask Sam to run
git fetchagain. The output shows* [new tag].
After the push, git ls-remote --tags origin shows two lines for the tag. The line that ends in ^{} shows the commit:
2131d28c763e2fbb027721bef294f9f7bf1e405b refs/tags/v1.0.0465d7ea589a41feac19db85754dd27d487d78f76 refs/tags/v1.0.0^{}Common mistake
I tagged the wrong commit
Symptom: you wanted to tag the release, but you were on main. git log --oneline --decorate -2 shows the tag on the unfinished work:
eaa41a2 (HEAD -> main, tag: v1.0.1, origin/main, origin/HEAD) Start the ticket export (not finished)465d7ea (tag: v1.0.0, origin/release/1.0, release/1.0) Add escalation rulesCause: git tag tags the commit that HEAD points to, unless you give a commit. You were on the wrong branch.
Fix: if you did not push the tag, delete it and tag the correct commit:
-
Delete the local tag:
Terminal git tag -d v1.0.1Output Deleted tag 'v1.0.1' (was edbb130)edbb130is the tag itself, not a commit. -
Switch to the correct branch, for example
git switch release/1.0. -
Make the tag again with
git tag -a.
If you try to make a tag with a name that exists, Git refuses. For example, git tag -a v1.0.0 -m "x" shows fatal: tag 'v1.0.0' already exists.
Check your understanding
Predict: Sam merges three more pull requests into main. Which of main, release/1.0 and v1.0.0 move?
Show the answer
Only main moves. The pull requests target main, so only main gets the new commits. release/1.0 moves only when someone merges a fix into it. v1.0.0 never moves, so the help desk keeps exactly version 1.0.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Practical check: show which commit each reference points to, and prove that the tag is on the remote.
Show the answer
Run git log --oneline --graph --all --decorate. Each commit shows its branch and tag names, for example (tag: v1.0.0, origin/release/1.0, release/1.0). Then run git ls-remote --tags origin. If the output lists refs/tags/v1.0.0, the remote has the tag.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
mainis for development. It moves with every merged change.- A release branch, for example
release/1.0, is the release candidate. You make it frommain, and it accepts fixes only. - A tag, for example
v1.0.0, marks the released version. It never moves. Usegit tag -afor releases. git pushdoes not send tags. Push each tag withgit push origin <tag>.- Versions use MAJOR.MINOR.PATCH. A fix changes only the PATCH number.
Next: Cherry-pick a fix