PES Guide

Git in depth

Git in a team: protections and good practices

Knowing how to commit is not enough when five people touch the same code. This page explains, step by step, how to protect the main branch, how to review each other, how to avoid scares and what to do when you have one. It is not slide material: it is here for you to consult when you need it.

Git in depth

Protect the main branch

The main branch is what you hand in and what you deploy. One wrong push can break Wednesday's demo. Branch protection makes GitHub stop the mistakes instead of hoping nobody slips.

How to turn it on in GitHub

  1. Open the repository and go to Settings, then Rules and Rulesets (or Branches, in the classic "branch protection rules" view).
  2. Create a new rule for the default branch (main) and, if you use Gitflow, another for develop.
  3. Set it to Active and tick the options in the list below.
  4. Test it: try a direct git push to main. It must be rejected.

It depends on the plan. The exact names can change a little in GitHub's interface, and on private repositories whether the rules are enforced depends on the organisation's plan (if the buttons are greyed out or the rule does not apply, write it into your team agreement and watch it by hand). Ask what your group's organisation has enabled.

What to tick and why

Require a pull request before merging

Nobody pushes code to main directly: everything goes through a PR. Ask for 1 approval at least.

Dismiss stale approvals

If commits are added after approval, the approval expires and the PR needs another review.

Require status checks to pass

The PR cannot be merged if continuous integration fails (see below).

Require conversation resolution

No merging with unresolved review comments.

Block force pushes

Nobody can rewrite main's history with --force.

Restrict deletions

Nobody can delete the branch by accident.

Do not allow bypassing

Not even the repository admin skips the rules. That way there are no "just this once" exceptions.

Pull requests that can be reviewed

A pull request (PR) is a proposed change with a conversation. If it is small and clear, the review is quick and useful; if it is huge, nobody really reads it and it gets approved by eye.

A good PR

  • One task, one PR. If it changes more than 300-400 lines or does two things, split it.
  • A meaningful title, preferably in Conventional Commits format: feat(signup): validate the email.
  • Link the task in Taiga (or a Closes #34 if you use GitHub issues, which closes the issue on merge).
  • Two lines of context: what changes, why and how you tested it. Screenshots if it touches the UI.
  • Review your own diff first before asking for a review: you will spot forgotten prints and files that do not belong.
  • Draft pull request if you want feedback on work in progress.

When you review

  • Someone other than the author does it; rotating reviewers spreads knowledge.
  • Check first that it does what the story's acceptance criteria say, then edge cases, tests and naming.
  • Comment on the code, not the person: ask ("why not use...?") before ordering.
  • Separate what is essential from what is optional (nit) so the author knows what must be fixed.

PR template (optional but useful)

If you create the file .github/pull_request_template.md, GitHub uses it as the starting text of every new PR:

## What changes
(One or two sentences: what this PR does and why.)

## Ticket
Taiga: #34

## How it was tested
- [ ] Automated tests
- [ ] Tried by hand (explain how)

## Notes for the reviewer
(What should be looked at most carefully? Screenshots if the UI changes.)

Merge, squash or rebase: how to merge

GitHub offers three ways to merge a PR and each leaves a different history. Choosing one is a team decision, not each person's.

OptionWhat it doesResulting historyWatch out for
Create a merge commitAdds all the branch's commits plus a merge commit.Faithful: every commit and who made it is visible.Many merge commits if it is not kept tidy.
Squash and mergeSquashes the whole PR into one commit on main.Clean: one commit per task (the PR title becomes the message).The commit detail is lost and credited to whoever opens the PR.
Rebase and mergeReplays the branch's commits on top of main.Linear, no merge commits, keeping every commit.Rewrites hashes; do not use it on shared branches.

Recommendation for PES: the course looks for everyone committing often and evenly. With squash, all of a PR's work ends up under one person, and if you pair-program it hides who did what. That is why Create a merge commit or Rebase and merge fits better. If in a pair only one account makes the commit, add the other person with a Co-authored-by: Name <email> footer so GitHub counts it for them.

Also turn on Automatically delete head branches (Settings, General): on merge, GitHub deletes the task's branch so they do not pile up.

Continuous integration: main's doorman

Continuous integration (CI) runs the tests, the linter and the build on every PR on a clean machine. If CI is a required status check of the protection, a PR that breaks the tests cannot be merged.

  1. Create the file .github/workflows/ci.yml (example below, for Node: adapt it to Java, Python or Android).
  2. Open a PR: in the Actions tab you will see it run.
  3. In main's protection rule, under Require status checks, add the test job.
  4. Keep it fast (under 5 minutes) and, if it fails on main, fix that before anything else.

CI does not replace tests: it runs them automatically and identically for everyone. Later you can deploy to virtech only from main and only if CI passed.

name: CI
on:
  pull_request:
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm test

Keep your branch up to date

The longer a branch lives, the harder it is to merge. Bring in main's changes every day and keep branches short (two or three days, not two weeks).

git switch main
git pull
git switch feature/34-sign-up
git rebase main             # or: git merge main
git push --force-with-lease   # only after a rebase and only on your own branch
  • Merge is the safest: it rewrites nothing and works on branches other people also use.
  • Rebase leaves a linear history but rewrites commits: never do it on a branch someone else has already pulled.
  • --force-with-lease fails if someone pushed something you do not have; far safer than --force.
  • If two people touched the same line, Git marks the conflict. Open the file, keep what is right, delete the markers, run git add and continue (git rebase --continue or make the merge commit). If you get lost, git rebase --abort or git merge --abort take you back to the start.

This is what a conflict looks like

<<<<<<< HEAD
your change
=======
the other person's change
>>>>>>> main

Secrets and files that must not be pushed

A GitHub repository is easy to clone, copy and remember forever. What goes up, stays up.

  • A .gitignore from the first commit: node_modules/, build/, .env, *.log, IDE folders, .apk files and signing keys.
  • Never push passwords, API tokens, private keys or the .env. Add a .env.example with empty variables so teammates know which ones are needed.
  • For CI, store values in Settings, Secrets and variables, Actions, not in the code.
  • If one leaks, revoke it and generate a new one right now. Deleting the commit is not enough: history, forks and caches keep it.
  • Do not push big binaries (APKs, videos). For executables use GitHub Releases.

Tags and versions

A tag is a permanent name for a commit. Tag every delivery and you can always go back to exactly what you handed in.

git tag -a fag-3 -m "Fag-3 delivery"
git push origin fag-3
  • On GitHub, Releases lets you attach notes and the executable to a tag.
  • For versions users see, use Semantic Versioning: MAJOR.MINOR.PATCH (1.4.2). A fix bumps PATCH, a feature MINOR and a breaking change MAJOR.
  • Conventional Commits types (fix, feat, !) tell you which number is next: see Conventional Commits.

When something goes wrong

Everyone makes mistakes with Git. The difference is knowing how to undo them without making things worse. Golden rule: if you already pushed it to a shared branch, do not rewrite history: make a new commit that fixes it.

What happenedWhat to do
I committed on main by mistake (not pushed)git switch -c feature/34-x (the commits are now on the new branch), then git switch main and git reset --hard origin/main. Careful: reset --hard discards unsaved changes.
The last commit message has a mistake (not pushed)git commit --amend
I forgot a file in the last commit (not pushed)git add file and git commit --amend --no-edit
I staged a file by mistakegit restore --staged file
I want to discard a file's changesgit restore file (cannot be undone)
I pushed a bad commit to a shared branchgit revert <hash>: creates a commit that undoes it. Do not reset or --force.
The push is rejected ("non-fast-forward")Someone pushed first: git pull --rebase (or git pull) and push again. Do not force.
I think I lost commitsgit reflog lists where HEAD has been; git switch -c rescue <hash> brings them back.

Team agreement about Git

Write down in the report what you decided and review it at every retrospective. This list is a guide; it is saved in your browser.

Team agreement

0/10

Back to Taiga and GitHub →