Milestone 2 · Software Technology

Working as a team in git: issues, branches, merge requests

Milestone 2: the team workflow – protected main and develop, a branch per issue, commit messages checked by git hooks, labelled issues on a board, and reviewed merge requests merged with merge commits.

14 min readmain, develop, feature branchesBranch and commit conventionsCommit hooksIssues, labels, milestonesMerge requestsCommon team mistakes

Milestone 2, in week 7, is the first prototype: about 30% of the use cases work, and version control is working and actively used by every member. From this milestone on, your GitLab activity – issues, branches, commits, merge requests, reviews – weighs heavily in your score, because it is the record of who did what.

This page is the team workflow the course recommends, as packaged in its template repository: two protected branches, a branch per issue, a fixed commit format checked by git hooks, and merge requests reviewed by a teammate. It assumes you know commits, branches and merges; if not, read Git and GitHub: the mental model, in pictures first.

The rules on one screen

WhatRule
Branchesmain = released state, develop = integration. Both protected: nobody pushes to them, code arrives only through merge requests.
Branch namefeature/ABC-12-short-title (or bugfix/), where ABC-12 is issue #12. One issue = one branch. Starts from develop, returns to it.
CommitABC-12: add save file validation – imperative, English, at most 72 characters. The hook adds the prefix from the branch name.
IssueAll work starts from an issue: template, milestone, assignee, type label.
BoardTodo → Doing → Review → Closed. You move the card; the merge request closes the issue.
Merge requestTemplate filled, one approval from a teammate, green pipeline, all threads resolved, merge commit. The author merges.
Definition of DoneAcceptance criteria met, unit tests for the model, pipeline green, MR merged, wiki updated.

ABC stands for your project's 2–5 letter key; the template uses KEY as a placeholder that you replace once. Older course material says master; new GitLab projects create main – same role, different name.

Branches: main, develop, one per issue

Two long-lived branches carry the project. develop is where finished work comes together, and it should always build. main only moves when the team releases a milestone. All actual work happens on short-lived branches that start from develop and come back through a merge request (Fig. 1).

mainrelease/v1.0developfeature/ABC-12feature/ABC-13a1fb20c31c32m12d41m13r01v10mb1v1.0MR !1MR !2main → developbrings the release backFeature branches live for days, not weeks. Nobody commits on main or develop directly.
Fig. 1 · Feature branches start from develop and return to it. A release branch carries a milestone into main, where it is tagged; main then goes back into develop.
BranchPurposeFromBack into
mainReleased, demoed state. Tagged after every merge.––
developIntegration. Feature MRs land here; always builds.––
feature/ABC-12-…One issue: a feature, but also a refactor, tests, docs.developdevelop
bugfix/ABC-34-…A bug found on develop.developdevelop
release/v1.0Finishing a milestone release: version, README, small fixes.developmain, then main → develop
hotfix/ABC-56-…An urgent fix on the released version.mainmain, then main → develop

Branch names

feature/ABC-12-implement-save-load     ← issue #12, a new feature (also: refactor, tests, docs)
bugfix/ABC-34-fix-wall-collision       ← issue #34, a bug found on develop
release/v1.0                           ← preparing the milestone release, no issue number
hotfix/ABC-56-crash-on-empty-save      ← issue #56, urgent fix on the released version

anna-branch, peter-dev, test2, final   ← not branch names: they say who, not what

The issue page in GitLab has a Create merge request / Create branch button that generates the name for you, in exactly this form if the project's branch name template is set. And push your branch every day, finished or not: it is your backup, and it shows the team where you are.

Issues, labels, milestones, the board

Every piece of work is an issue – a feature, a bug, a refactor, a test, a page of documentation, CI work. No issue, no branch, no commit. An issue is created from a template (Feature or Bug), has a short imperative title (Implement save/load), exactly one assignee, a milestone and a type label. If two people work on something, it is two issues.

type::…
feature, bug, refactor, test, docs, ci – what kind of work it is.
status::…
todo, doing, review – the board column. Scoped labels exclude each other, so an issue sits in one column.
priority::high
Only when it matters.
Milestone
M1 … M4, due on the weeks 4, 7, 10 and 13. What doesn't fit moves to the next one – it never hangs in the air.

The templates end with GitLab quick actions, which run when the issue is saved – that is how every new issue gets its labels without anyone remembering to add them:

The end of an issue description

## Acceptance criteria
- [ ] Save creates a file in the user's save folder
- [ ] Error case: loading a corrupt file keeps the current game

/label ~"type::feature" ~"status::todo"
/milestone %"M2 Prototype I"
/assign me

The path of one issue across the board and through git (Fig. 2):

issue boardchanges requestedTodoDoingReviewClosedwhat you do1 · Create the issuetemplate, milestone, you2 · Branch + commitsfeature/ABC-12-…3 · Open the MRCloses #12 · a reviewer4 · Mergethe issue closes itself
Fig. 2 · You move the card to Doing when you start and to Review when you open the merge request. Closing is done by the merge, through Closes #12 – not by hand.

From issue #12 to a pushed branch

git switch develop
git pull
git switch -c feature/ABC-12-implement-save-load
# ... work, then commit in small steps:
git add -p
git commit -m "add save file serialization"      # the hook makes it "ABC-12: add save ..."
git push -u origin feature/ABC-12-implement-save-load

The full command sequence, including updating your branch and cleaning up after the merge, is in the issue lifecycle cheat sheet.

Commit messages and hooks

The format is ABC-12: imperative summary. Imperative, in English: add, fix, remove – read it as "this commit will … add save file serialization". One commit is one logical step. If a reason needs explaining, add a body after a blank line: the body says why, the diff already says what.

Good and bad first lines

ABC-12: add save file serialization          ✓ imperative, says what this step does
ABC-12: handle missing save directory        ✓ one logical step
ABC-34: fix crash when settings file is empty ✓

ABC-12: fixed stuff.                         ✗ past tense, says nothing, ends with a period
wip                                          ✗ no key, says nothing – the hook rejects it
ABC-12: update                               ✗ "update" what?

A commit with a body

ABC-84: retry save when the target directory is locked

On Windows the save directory can be locked for a moment by the
antivirus; a single retry after 100 ms removes the spurious failure.

You don't type the prefix. Two git hooks in the repository's githooks/ folder do the bookkeeping (Fig. 3): prepare-commit-msg reads the issue key from the branch name and prepends it, commit-msg checks the result and refuses a bad message with an explanation.

git commit -m …add save file validationprepare-commit-msgadds the issue keycommit-msgchecks the formatcommit createdin git logbranch: feature/ABC-12-save-loadrejectedthe hook says whychecksABC-12: prefix≤ 72 charactersno final periodnot just “wip”what lands in the history:ABC-12: add save file validation
Fig. 3 · The branch name carries the issue key, the hook copies it into every commit, and the second hook rejects messages that would make the history useless.

Hooks are not cloned automatically – each member switches them on once per clone. They run inside git itself, so they work from the terminal, IntelliJ, Rider, Visual Studio, VS Code and GitHub Desktop alike (on Windows through Git for Windows' bundled bash).

Once per clone, every member

git clone <repo-url>
cd <repo>
./setup-hooks.sh                 # once per clone; Windows cmd / PowerShell: .\setup-hooks.bat
# it runs: git config core.hooksPath githooks

The core of the two hooks

# prepare-commit-msg: take the key from the branch name ...
TICKET=$(printf '%s' "$BRANCH" | sed -nE 's#^[a-z]+/([A-Za-z]{2,5}-[0-9]+).*#\1#p')

# commit-msg: ... and reject a first line that doesn't follow the rules
printf '%s' "$FIRST_LINE" | grep -qE '^([A-Z]{2,5}-[0-9]+|release|hotfix|chore|docs|ci): .+' \
  || fail "missing or invalid prefix"
[ ${#FIRST_LINE} -gt 72 ] && fail "subject line is too long"

Why ABC-12 and not #12?

  • In git, # at the start of a line is a comment: a message written in the editor as #12: add … loses its first line and git aborts with "empty commit message".
  • A key says which project the issue belongs to, even when the commit is quoted somewhere else. It is the form Jira and most companies use, so the habit transfers.
  • GitLab links the issue through the branch name and the Closes #12 line of the merge request; the key in the message is for people reading git log and git blame.

Merge requests

A merge request (MR; on GitHub: pull request) asks the team to take your branch into develop. Its title is the issue's title; its description follows the template – what and why, how you tested it, and Closes #12. Open it early with Draft: in the title if you want feedback before it is ready.

The platform enforces four merge conditions:

  1. at least one approval from a teammate – never from the author,
  2. a green pipeline (from M3; until then the setting accepts skipped pipelines),
  3. every review thread resolved,
  4. no conflict with develop – if there is one, the author resolves it, not the reviewer.

After the approval, the author merges, because the author knows whether everything is done. The merge method is a merge commit – no squash, no fast-forward (Fig. 4).

Merge commit – what we used0abcMevery step stays visible, with its authorSquash merge – switched off hereabcd0Sthree steps become one; the work log is goneIf a team ever squashes, the feature branch is the only record of the steps – don't delete it.
Fig. 4 · A merge commit keeps every step and its author and marks where the MR ended. A squash replaces them with one commit – common in companies, switched off in this course because grading reads the individual commits.

With merge commits, deleting the source branch after the merge loses nothing – the commits are in develop – so GitLab is set to delete it automatically. How to write and answer a review is in the code review guide.

Common mistakes

Three patterns show up every semester, and all three are visible in the commit graph (Fig. 5).

Branches named after peopleannapetermasterm0a1a2a3a4p1p2p3!!weeks of drift, one huge conflicting mergeA branch per issuedevelopd0d1d2d3f1g1h1ABC-12ABC-13ABC-14small, short-lived, merged every few days
Fig. 5 · A branch per person drifts for weeks and ends in a merge nobody can review. A branch per issue lives for days and merges cleanly.

What the M2 demo shows

Show the graph in the demo. A minute on the repository – the board, the MR list, the network graph – tells the instructor more about the process than any slide.

Summary

  • main = released, develop = integration; both protected. Work happens on one short-lived branch per issue.
  • Branch: feature/ABC-12-short-title. Commit: ABC-12: imperative summary – the hooks add and check the prefix.
  • Every piece of work is an issue with a template, a milestone, one assignee and a type label.
  • Merge requests need a teammate's approval, a green pipeline and resolved threads; the author merges with a merge commit.
  • Avoid: everything on master, branches named after people, one person committing for the team.