Menu for Per-Arne's Git Good!
Courses and project Per-Arne's Git Good!

Final challenge: from solo to team

Module 6: Final challenge · 35 min

The interactive lesson needs JavaScript. The text below is the complete lesson.

After this lesson you can

  • carry out the whole path from feature branch to published main in real Git, without a demo apply
  • choose merge or rebase from whether the branch is shared, and provoke, abort and resolve a conflict evaluate
  • choose revert on shared history and reset only on a disposable branch, and rescue work with the reflog evaluate
  • publish without overwriting a colleague, even after a rejected push apply

Understand: The assignment

This is the final challenge. No demo this time. You take a small feature of two commits from a feature branch into the team's main, while Ada works in her own clone. Everything happens locally, with origin as a bare repository.

Ada publishes something on main along the way. You fetch, integrate with merge or rebase, and meet a conflict that you first abort and then resolve. In the end everything lands in main in origin.

Then you make a deliberate mistake on shared history and undo it with a new commit, not by moving main backwards. You practise reset and reflog only on a disposable branch that nobody else has.

You are done when the working tree is clean, no conflict markers are left, and none of Ada's commits has been overwritten. You must be able to explain every command you used.

The course checks the result of each task, not the wording, so every correct route passes. If you use --force or another destructive option the task does not need, you have to restart the challenge from the first task. The checklist is under Read more.

Practise: The deliverables, step by step

You and Ada each have a clone of origin, and everyone is on the same commit. The tasks build on each other in order. Some you do as Ada; the terminal switches to her clone.

Tasks

  1. Create a feature branch called feature and make at least two coherent commits on it. Look at the staged diff before each commit. Leave README.md alone, you need it later. Leave the working tree clean.
  2. As Ada: publish a different change on main. Create a new file, for example schedule.txt, commit it and push.
  3. Back in your repository: show where main and origin/main point, get the news from origin, and show the same again. Your own main must not move.
  4. Integrate Ada's commit into feature, with merge or rebase. Your choice: note which IDs are new.
  5. As Ada: reword the first line of README.md on main, commit and publish.
  6. In your repository, on feature: reword the same first line of README.md differently from Ada, and commit. Do not fetch her change yet.
  7. Fetch and integrate Ada's new commit into feature. It stops with a conflict. Stop there and look at git status.
  8. Abort the integration, and confirm that feature is exactly as before, without Ada's new commit.
  9. Integrate again, and resolve the conflict with final content you can justify. Remove the markers and finish.
  10. Integrate feature into main and publish main to origin.
  11. Make a deliberate mistake on main: commit a file debug.log that does not belong in the repository, and push it. Now the mistake is in shared history.
  12. Undo the mistake in a way that suits shared history, and publish the fix. The bad commit must still be in the history.
  13. Create a disposable branch experiment from main, make one commit on it, and throw the commit away in the way that suits a branch only you have, so the branch is back on main.
  14. You change your mind. Find the discarded commit in the reflog, preserve it in a branch called rescue, and check its content.
  15. As Ada: update main from origin, make one more commit and publish it.
  16. Final delivery: on main, add CHANGELOG.md in a commit and publish it to origin without overwriting Ada's work. The first push will be rejected.

Check yourself: Justify your choices

You find two bad commits. X is on the branch experiment, which only you have. Y is on main in origin, and Ada has already pulled it. What do you do?

  • Reset both, and publish main with git push --force-with-lease
  • Reset experiment past X, and revert Y with a new commit
  • Reset both, and push main as usual

Your push to main is rejected. You have just run git fetch. A fellow student suggests git push --force-with-lease because it is safe. What happens?

  • It refuses, because origin has moved
  • It merges Ada's commit in automatically
  • It succeeds, and Ada's commit disappears from main in origin

Yesterday you pushed feature so Ada could review it, and she has fetched it. Now main has new commits. How do you bring them into feature?

  • Rebase onto origin/main and force-push feature
  • Merge origin/main into feature and push as usual
  • Cherry-pick the new commits from main onto feature

You ran git reset --hard HEAD~1 on experiment and want to keep the commit after all. What secures it?

  • Nothing is needed: the reflog keeps it forever
  • A branch pointing at it, for example git branch rescue HEAD@{1}
  • git status, which shows commits that were reset away

Common misconceptions

  • “I fix a rejected push with --force-with-lease, since it is safe.” After a fetch the lease matches, and your colleague's commit disappears anyway.
  • “Reset and revert do the same thing.” Reset moves a branch backwards; revert adds a new commit that undoes an old one. Only revert suits shared history.
  • “The reflog is a backup.” It is local and it expires. Create a branch for what you want to keep.

Read more

The assignment

Take a small feature of two commits from your own machine into the team's shared history, with Ada working in her own clone at the same time. Everything runs locally: origin is a bare repository at /course/origin.git, Ada's clone is /course/ada, and no hosting account is needed. This is a self-check, not a graded exam.

You deliver seven things:

  1. A feature branch with at least two coherent commits. Look at git diff --staged before each commit.
  2. Ada publishes a different change on main. Show where your main and origin/main point before and after git fetch.
  3. Integrate Ada's work with merge or rebase, and justify the choice. Note the parents, and which IDs are new.
  4. Provoke a conflict in a tracked text file. Abort once, then repeat and resolve it with content you can justify.
  5. Publish a deliberate mistake on main and undo it in a way that suits shared history. Explain why a reset would not be the same.
  6. On a separate, disposable branch: reset away a commit, find it again with git reflog and preserve it in a branch called rescue.
  7. Publish only to the local origin, without overwriting Ada's work, even when your push is rejected.

Acceptance criteria

  • The working tree is clean when you finish, or any remaining changes are explained.
  • No conflict markers remain, and a relevant check of the behaviour passes.
  • No colleague's work has been overwritten to get past a rejected push: no --force, no --force-with-lease on main.
  • You can explain every command without pointing at a button in a GUI.

Showing your evidence

These read-only commands make the story visible. Run them often; none of them changes anything.

Question Command
Where am I, and is anything uncommitted? git status
What will the next commit contain? git diff --staged
What does the whole history look like? git log --oneline --graph --all
How far ahead or behind is each branch? git branch -vv
What are a commit's parents? git rev-list --parents -n 1 HEAD
Where did my branch point earlier? git reflog
What has origin got that I have not integrated? git log --oneline main..origin/main

A merge commit has two parents, so git rev-list --parents -n 1 HEAD prints three IDs: the commit itself and both parents. After a rebase, compare the IDs from git log --oneline with the ones you noted before: the replayed commits have new IDs even though their messages are the same.

Decisions you must be able to justify

Merge or rebase? Rebase your feature branch only while its commits exist on your machine alone; the result is a straight line that is easy to review. Once a branch is shared, merge instead, because rebasing it would hand your colleagues copies of commits they already have.

Revert, not reset, on shared history. git revert adds a new commit that undoes the change, so everyone who already has the bad commit can simply pull the fix. Resetting main and pushing would require a force push, would remove commits others have built on, and would leave their clones disagreeing with origin.

Never force past a rejection. A rejected push means origin has commits you have not integrated. The fix is fetch, integrate, test and push. --force-with-lease does not help here: once you have fetched, the lease matches and Ada's commit would still disappear.

Reset only where you are alone. A hard reset is acceptable on a disposable branch that nobody else has. Before you clean anything up, make sure the commit you want is reachable from a branch; the reflog is a local, temporary safety net, not a backup.

Self-assessment checklist

Tick each line honestly before you call the work done.

  • My feature branch has at least two commits, each with one purpose and a message that says why.
  • I can say where main, origin/main and main in origin pointed before and after my fetch.
  • I can name the parents of my integration commit, or the new IDs my rebase created.
  • I aborted the first conflict and saw that my branch was exactly as before.
  • My conflict resolution is a deliberate choice, not just one side, and no markers are left.
  • The bad commit is still in the history of origin, followed by a commit that undoes it.
  • The rescue branch points at the commit I reset away, and I checked its content.
  • Ada's latest commit is an ancestor of main in origin.
  • git status is clean.

Peer review

Swap logs with a fellow student. A reviewer should be able to reconstruct your main sequence of events from git log --oneline --graph --all and git reflog alone. Ask each other:

  • Which commit did Ada make, and how did it reach your branch?
  • Why did you choose merge or rebase, and what did that do to the IDs?
  • Where is the conflict resolution, and why is the final content right?
  • Which command undid the mistake, and why would a reset have been wrong?
  • What would have happened if you had used --force at the end?

Give one concrete suggestion for improvement each. If the two of you get different results from the same steps, compare branch, HEAD, configuration and working files before you conclude that Git is random. It never is.

Official documentation