Lesson 15 of 15

Final Project: Collaborative Workflow

What You Are Building

This project is not really about the code. You are going to build a small college fest website — a homepage, an events page and a registration form — but the point is the Git workflow around it. By the end you will have deliberately created and resolved a merge conflict, deliberately lost a commit and recovered it, and produced a repository whose history a stranger can read.

Do it with a partner if you can. Two people on one repository is where every idea in this course starts to matter, and a conflict you cause on purpose with a classmate teaches more in ten minutes than any amount of reading. If you are working alone, make two clones of the same repository in two different folders and treat them as two people — everything behaves identically.

Work through the stages in order and do not skip the parts that look obvious. The value is in the whole cycle running end to end: branch, commit, push, pull request, review, merge, tag. Once you have done that with your own hands, the sequence stops being a list of commands and becomes a habit you do not have to think about.

Set aside about two hours. Nothing here is individually difficult; the time goes into reading what Git tells you at each step instead of typing past it.

  • Create a GitHub repository with a README and a suitable .gitignore
  • Protect main so nothing can be pushed to it directly
  • Build three pages, each on its own feature branch
  • Open a pull request for each, and review at least one properly
  • Create a merge conflict on purpose and resolve it
  • Use git stash to switch tasks mid-edit
  • Lose a commit with git reset --hard and recover it with git reflog
  • Tag v1.0.0 and push the tag
Notes
  • Push at the end of every stage rather than at the end of the project. It is both your backup and the thing that makes the repository's history look like real work spread over time — which is exactly what you want if you later show it to anyone.

Stage 1: Set Up the Repository

Start on GitHub rather than locally. It is the easier direction and it avoids the unrelated-histories problem from the GitHub lesson. Create a repository called fest-website, tick the option to add a README, choose a .gitignore template, and then clone it.

Before writing any code, do the two things everyone skips. First, write the README properly: what the project is, who is working on it, and — the part that matters for the rest of this project — the workflow rules your team is following. Five lines is enough. Second, open Settings and add a branch protection rule for main that requires a pull request before merging.

That protection rule is what makes the rest of the project real. With it on, an accidental git push to main is rejected by the server, and every change is forced through the pull request flow. Without it you will take the shortcut at some point, because everybody does.

Verify the setup before moving on: git remote -v should show your repository, git branch -vv should show main tracking origin/main, and a push straight to main should be refused.

Example
# Create it on github.com with a README and a .gitignore template, then:
git clone git@github.com:yourname/fest-website.git
cd fest-website

git remote -v          # origin is already configured
git branch -vv         # main is tracking origin/main

# Confirm branch protection works (this push should be REJECTED)
git commit --allow-empty -m "test"
git push
# remote: error: GH006: Protected branch update failed

# Good. From here, everything goes through a branch.
git reset --hard origin/main      # discard that test commit

git switch -c docs/readme
#   ... write the README: scope, team, workflow rules ...
git add README.md
git commit -m "Describe the project scope and team workflow"
git push -u origin docs/readme

gh pr create --title "Add project README" --body "Sets out scope and workflow rules."
gh pr merge --squash --delete-branch

git switch main && git pull

Stage 2: Feature Branches and Pull Requests

Now build the site, one page per branch. Each cycle is identical: switch to main, pull, create a branch, work in several small commits, push, open a pull request, get it reviewed, merge, delete the branch, pull main again.

Resist the temptation to do all three pages on one branch. Three separate branches means three separate pull requests, three reviewable diffs and three chances to practise the cycle. It also means that if the events page turns into a mess, you can abandon that branch without losing the homepage.

Keep the commits small and honest. "Add events page markup", then "Style event cards", then "Make the grid responsive" is a history someone can follow. One commit called "events page" containing four hundred lines is not, and breaking that habit is much of the point of this exercise.

If you have a partner, review each other's pull requests properly: read the diff, run the branch locally with gh pr checkout, and leave at least one substantive comment. Approving without looking is the most common way student teams get no value at all out of pull requests.

Example
# One full cycle, repeated for each page

git switch main && git pull
git switch -c feature/homepage

#   ... create index.html ...
git add index.html
git commit -m "Add homepage markup"

#   ... create css/style.css ...
git add css/style.css
git commit -m "Add base styles and colour palette"

# Read your own diff before asking anyone else to
git diff main...HEAD

git push -u origin feature/homepage
gh pr create --title "Add homepage" --body "Static homepage with hero and nav. Closes #1."

# Your partner reviews it locally before approving
gh pr checkout 2
#   ... open it in a browser, leave line comments ...

# Merge, clean up, go again
gh pr merge --squash --delete-branch
git switch main && git pull
git fetch --prune

# Repeat for feature/events-page and feature/registration-form
Notes
  • Before opening each pull request, run git diff main...HEAD and read your change the way a reviewer would. You will find something to fix nearly every time, and catching it yourself is far faster than a round trip through review.

Stage 3: Cause a Conflict, Then Fix It

This stage is deliberate. Conflicts are the thing students fear most, and the only cure is to create one on purpose, in a project where nothing is at stake, and resolve it calmly.

Make two branches from the same starting commit and have both edit the same lines of the same file — the site title in the navigation bar is an easy target. Merge the first branch into main normally. Then merge the second, and Git will stop with a conflict.

Open the file and read the markers as a question rather than as damage. Everything above ======= is what main currently has; everything below is what your branch wants. Decide the correct final text — which may well be a combination rather than either version — delete all three marker lines, save, stage the file and commit.

Then do it a second time and, instead of resolving it, run git merge --abort. Watch the repository return to exactly its pre-merge state with nothing lost from either side. Knowing that this escape hatch exists, and having actually used it, is what turns a conflict from a crisis into a task.

Finish by opening the page in a browser. A conflict resolved into code that is syntactically valid but wrong is the failure mode nobody warns you about, and the only defence is running what you produced.

Example
# Set up the collision
git switch main && git pull
git switch -c feature/title-a
#   change the <h1> to "TechFest 2026"
git commit -am "Update site title"
git switch main
git merge feature/title-a

git switch -c feature/title-b main~1     # branch from BEFORE that change
#   change the same <h1> to "Annual TechFest"
git commit -am "Rename site title"

git switch main
git merge feature/title-b
# CONFLICT (content): Merge conflict in index.html

# index.html now contains:
# <<<<<<< HEAD
#   <h1>TechFest 2026</h1>
# =======
#   <h1>Annual TechFest</h1>
# >>>>>>> feature/title-b

# Decide, delete ALL marker lines, then:
git add index.html
git status               # confirms nothing else is unmerged
git commit

# Now practise the escape hatch on the next conflict you cause
git merge --abort        # back exactly to where you were
Notes
  • Search the whole project for <<<<<<< before committing a resolution. Committing conflict markers is extremely common, and it produces a file that is broken in a way that looks nothing like a Git problem to whoever finds it next.

Stage 4: Recovery Drills and a Release

The last stage rehearses the emergencies in a repository where they do not matter. Do them deliberately now, so that the first time you meet them for real you already know the moves.

First, stash. Start editing the registration form, leave it half-finished, and imagine an urgent bug elsewhere. Run git stash -u, switch branches, do the other work, come back, and git stash pop. Notice particularly that you needed -u to carry the brand-new file along with you.

Second, recovery. Commit something, then run git reset --hard HEAD~1 and watch the commit disappear from git log. Now bring it back using git reflog and a branch at the recovered hash. Doing this once removes most of the fear that surrounds Git, because you learn from experience that committed work is genuinely hard to lose.

Finally, release. Tag the finished version with an annotated tag and push the tag explicitly — ordinary pushes do not send tags. Then create a Release on GitHub from that tag with a short note describing what is in it. Your repository now has a permanent, named point you can always return to, and a front page a stranger can understand.

Take five minutes at the end to read your own history with git log --oneline --graph --all. If it tells the story of the project clearly, you have done the exercise properly. If it does not, you now know exactly which habits to change on the next one.

Example
# Stash drill
#   ... start editing register.html and create js/validate.js ...
git stash -u -m "WIP: registration validation"
git switch main
#   ... fix something urgent, commit, push ...
git switch feature/registration-form
git stash pop

# Recovery drill
git commit -am "Add form validation"
git log --oneline          # note the hash
git reset --hard HEAD~1    # the commit leaves the branch
git log --oneline          # confirm it is not there
git reflog                 # but Git still knows about it
git switch -c recovered <hash-from-reflog>

# Release
git switch main && git pull
git tag -a v1.0.0 -m "First complete version: homepage, events, registration"
git push origin v1.0.0     # tags are NOT sent by a plain git push
git tag                    # list tags
git show v1.0.0

# Read your own history
git log --oneline --graph --all --decorate
  • Try git bisect: introduce a bug a few commits back, then use git bisect start, git bisect bad and git bisect good to narrow down which commit caused it
  • Add a GitHub Actions workflow in .github/workflows/ that runs on every pull request
  • Publish the site with GitHub Pages and put the live link in the README
  • Add a CONTRIBUTING.md describing your branch naming and commit message rules
  • Use git revert on a commit already merged into main, and compare how it feels against reset
  • Fork someone else's public repository, fix a genuine typo in their documentation, and open a pull request
  • Keep using Git for everything, including projects nobody else will ever see — it is a set of habits, and habits only form with repetition
Ask AI