Final challenge: from solo to team
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
- Create a feature branch called
featureand 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. - As Ada: publish a different change on main. Create a new file, for example
schedule.txt, commit it and push. - 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.
- Integrate Ada's commit into
feature, with merge or rebase. Your choice: note which IDs are new. - As Ada: reword the first line of
README.mdon main, commit and publish. - In your repository, on feature: reword the same first line of
README.mddifferently from Ada, and commit. Do not fetch her change yet. - Fetch and integrate Ada's new commit into feature. It stops with a conflict. Stop there and look at
git status. - Abort the integration, and confirm that feature is exactly as before, without Ada's new commit.
- Integrate again, and resolve the conflict with final content you can justify. Remove the markers and finish.
- Integrate feature into main and publish main to origin.
- Make a deliberate mistake on main: commit a file
debug.logthat does not belong in the repository, and push it. Now the mistake is in shared history. - Undo the mistake in a way that suits shared history, and publish the fix. The bad commit must still be in the history.
- Create a disposable branch
experimentfrom 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. - You change your mind. Find the discarded commit in the reflog, preserve it in a branch called
rescue, and check its content. - As Ada: update main from origin, make one more commit and publish it.
- Final delivery: on main, add
CHANGELOG.mdin 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
experimentpast 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
featureand 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:
- A feature branch with at least two coherent commits. Look at
git diff --stagedbefore each commit. - Ada publishes a different change on
main. Show where yourmainandorigin/mainpoint before and aftergit fetch. - Integrate Ada's work with merge or rebase, and justify the choice. Note the parents, and which IDs are new.
- Provoke a conflict in a tracked text file. Abort once, then repeat and resolve it with content you can justify.
- Publish a deliberate mistake on
mainand undo it in a way that suits shared history. Explain why a reset would not be the same. - On a separate, disposable branch: reset away a commit, find it again with
git reflogand preserve it in a branch calledrescue. - 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-leaseonmain. - 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/mainand 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
rescuebranch points at the commit I reset away, and I checked its content. - Ada's latest commit is an ancestor of
mainin origin. git statusis 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
--forceat 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
- gitworkflows
- git revert, git reset and git reflog
- git push, especially the note about fast-forwards
Per-Arne's Git Good!