Project: A Team Workflow from Start to Finish
This final project puts the whole course together. Two people, Sam and Alex, build a small recipe website together: they create a repository, share it on GitHub, work on feature branches in parallel, review each other's work, resolve a conflict, and tag a release. Every command was run for real — follow along with two folders to play both roles.
Step 1 — Sam creates the project
mkdir recipe-site cd recipe-site git init --quiet printf '<h1>Family Recipes</h1>\n<ul>\n <li>Pancakes</li>\n</ul>\n' > index.html printf 'node_modules/\n.env\n*.log\n' > .gitignore printf '# Recipe Site\n\nOur family recipes, online.\n' > README.md git add . git commit -m "Create recipe site with home page"
[main (root-commit) ad384c4] Create recipe site with home page 3 files changed, 10 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html
Step 2 — Publish it
Sam creates an empty recipe-site repository on GitHub (no README), then connects and pushes:
git remote add origin https://github.com/sam-rivera/recipe-site.git git push -u origin main
Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 10 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (5/5), 425 bytes | 425.00 KiB/s, done. Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/recipe-site.git * [new branch] main -> main branch 'main' set up to track 'origin/main'.
Step 3 — Alex joins
Sam adds Alex as a collaborator on GitHub. Alex clones the repository and sets their identity for it:
cd ~ git clone https://github.com/sam-rivera/recipe-site.git alex-site cd alex-site git config user.name "Alex Chen" git config user.email "alex@example.com" git log --oneline
Cloning into 'alex-site'... remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (3/3), done. remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 Receiving objects: 100% (5/5), done. ad384c4 (HEAD -> main, origin/main, origin/HEAD) Create recipe site with home page
Step 4 — Two features in parallel
Alex works on a desserts section:
git switch -c feature/desserts printf '<h1>Family Recipes</h1>\n<ul>\n <li>Pancakes</li>\n <li>Brownies</li>\n</ul>\n' > index.html git commit -qam "Add brownies to the recipe list" printf '<h1>Brownies</h1>\n<p>Chocolate, butter, sugar, eggs, flour.</p>\n' > brownies.html git add brownies.html git commit -qm "Add brownie recipe page" git push -u origin feature/desserts
Switched to a new branch 'feature/desserts' Enumerating objects: 8, done. Counting objects: 100% (8/8), done. Delta compression using up to 10 threads Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 659 bytes | 659.00 KiB/s, done. Total 6 (delta 1), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/recipe-site.git * [new branch] feature/desserts -> feature/desserts branch 'feature/desserts' set up to track 'origin/feature/desserts'.
At the same time, Sam works on breakfast recipes in the other folder:
cd ~/recipe-site git switch -c feature/breakfast printf '<h1>Family Recipes</h1>\n<ul>\n <li>Pancakes</li>\n <li>Shakshuka</li>\n</ul>\n' > index.html git commit -qam "Add shakshuka to the recipe list" git push -u origin feature/breakfast
Switched to a new branch 'feature/breakfast' Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 10 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 324 bytes | 324.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/recipe-site.git * [new branch] feature/breakfast -> feature/breakfast branch 'feature/breakfast' set up to track 'origin/feature/breakfast'.
Step 5 — Review and merge the first pull request
Both open pull requests. Sam reviews Alex's desserts PR, approves it, and merges it on GitHub with a merge commit:
git fetch --prune git log --oneline --graph --all
remote: Enumerating objects: 9, done. remote: Counting objects: 100% (9/9), done. remote: Compressing objects: 100% (7/7), done. remote: Total 7 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (7/7), 849 bytes | 424.00 KiB/s, done. From https://github.com/sam-rivera/recipe-site ad384c4..e397a0b main -> origin/main * e397a0b (origin/main) Merge pull request #1 from sam-rivera/feature/desserts |\ | * d5b34bf Add brownie recipe page | * b5f4efc Add brownies to the recipe list |/ | * a771dcf (HEAD -> feature/breakfast, origin/feature/breakfast) Add shakshuka to the recipe list |/ * ad384c4 (main) Create recipe site with home page
Step 6 — Update the second branch and resolve a conflict
GitHub reports that Sam's breakfast PR now has conflicts: both branches added a line at the same spot in index.html. Sam rebases the branch onto the new main:
git rebase origin/main
Auto-merging index.html CONFLICT (content): Merge conflict in index.html error: could not apply a771dcf... Add shakshuka to the recipe list hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply a771dcf... Add shakshuka to the recipe list
cat index.html
<h1>Family Recipes</h1> <ul> <li>Pancakes</li> <<<<<<< HEAD <li>Brownies</li> ======= <li>Shakshuka</li> >>>>>>> a771dcf (Add shakshuka to the recipe list) </ul>
Both recipes should stay. Sam writes the combined list, stages it and continues:
printf '<h1>Family Recipes</h1>\n<ul>\n <li>Pancakes</li>\n <li>Shakshuka</li>\n <li>Brownies</li>\n</ul>\n' > index.html
git add index.html
GIT_EDITOR=true git rebase --continue
git push --force-with-lease
[detached HEAD 6ba611e] Add shakshuka to the recipe list 1 file changed, 1 insertion(+) Successfully rebased and updated refs/heads/feature/breakfast. Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 10 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 338 bytes | 338.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/recipe-site.git + a771dcf...6ba611e feature/breakfast -> feature/breakfast (forced update)
The force push is safe here: feature/breakfast is Sam's own branch, and --force-with-lease would refuse if anyone else had pushed to it.
Step 7 — Merge with squash
Alex reviews and merges the breakfast PR, this time with Squash and merge:
git switch main git pull --prune
Switched to branch 'main' Your branch is behind 'origin/main' by 3 commits, and can be fast-forwarded. (use "git pull" to update your local branch) From https://github.com/sam-rivera/recipe-site - [deleted] (none) -> origin/feature/breakfast remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), 319 bytes | 319.00 KiB/s, done. e397a0b..409dfb2 main -> origin/main Updating ad384c4..409dfb2 Fast-forward brownies.html | 2 ++ index.html | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 brownies.html
git branch -d feature/breakfast
error: The branch 'feature/breakfast' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature/breakfast'.
As explained in lesson 36, a squash merge creates a new commit, so Git can't tell the branch was merged. Sam checks the PR on GitHub shows "Merged", then deletes the branch with -D:
git branch -D feature/breakfast git log --oneline --graph
Deleted branch feature/breakfast (was 6ba611e). * 409dfb2 (HEAD -> main, origin/main) Add shakshuka to the recipe list (#2) * e397a0b Merge pull request #1 from sam-rivera/feature/desserts |\ | * d5b34bf Add brownie recipe page | * b5f4efc Add brownies to the recipe list |/ * ad384c4 Create recipe site with home page
Step 8 — Tag a release
git tag -a v1.0.0 -m "First release: pancakes, shakshuka and brownies"
git push origin v1.0.0
Enumerating objects: 1, done. Counting objects: 100% (1/1), done. Writing objects: 100% (1/1), 188 bytes | 188.00 KiB/s, done. Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/recipe-site.git * [new tag] v1.0.0 -> v1.0.0
Step 9 — Alex catches up
Alex switches back to main, pulls everything — including the tag — and cleans up:
cd ~/alex-site git switch main git pull --prune
Switched to branch 'main' Your branch is up to date with 'origin/main'. From https://github.com/sam-rivera/recipe-site - [deleted] (none) -> origin/feature/desserts remote: Enumerating objects: 9, done. remote: Counting objects: 100% (9/9), done. remote: Compressing objects: 100% (5/5), done. remote: Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (5/5), 685 bytes | 342.00 KiB/s, done. ad384c4..409dfb2 main -> origin/main * [new tag] v1.0.0 -> v1.0.0 Updating ad384c4..409dfb2 Fast-forward brownies.html | 2 ++ index.html | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 brownies.html
git branch -d feature/desserts git describe
Deleted branch feature/desserts (was d5b34bf). v1.0.0
What you practised
| Step | Lessons |
|---|---|
| Create a repository, ignore files, commit | 05, 08–10, 14 |
| Publish to GitHub, clone | 30–33 |
| Feature branches and pushing with upstreams | 22–23, 32, 35 |
| Pull requests, merge commits and squash merges | 24, 36 |
| Rebasing and resolving a conflict | 25–26 |
| Force-pushing safely, pruning, deleting branches | 32, 35 |
| Tagging a release | 39 |
Where to go next
- Use Git for everything you write — code, notes, configuration — until the commands become habits.
- Contribute a small fix to an open-source project using the fork workflow (lesson 37).
- Add a CI workflow to one of your repositories (lesson 49).
- Test yourself with the exercises and the quiz, and keep the cheat sheet handy.