The project
You will build a repository called recipe-book that holds a few recipes as Markdown files. The recipes are not the point. The point is that the repository's history is the thing being graded: a reader who runs git log should see a clear, honest story, and a reader who opens your GitHub page should see a working pull request.
You will use every skill in this module: configure Git, make small commits with good messages, ignore private files, branch, merge, resolve a conflict you cause on purpose, push to GitHub, and open a pull request.
Plan on about two hours. Work in a fresh folder, and type the commands yourself. Do not copy the reference solution until you have tried.
The spec
Your finished project must have this shape.
Files in the final repository (on main):
| File | Content |
|---|---|
README.md | Title, a one-sentence description, and a bullet list linking each recipe |
chai.md | A recipe with a title and an ingredient list |
poha.md | A second recipe |
.gitignore | Ignores .env, *.log and a folder named notes-private/ |
Files that must exist on your disk but must NOT appear in git ls-files:
.envcontaining the lineAPI_KEY=not-a-real-keydebug.logwith any textnotes-private/ideas.txtwith any text
History requirements:
- At least 6 commits on
mainbesides merge commits. - Every message in the imperative, under 50 characters, and meaningful (no "update", "stuff", "fix").
- The first commit contains the
.gitignore, so no private file is ever tracked. - One merge that is a real conflict on
README.md, resolved by you, with both ideas kept. - One feature branch merged with
--no-ff, so the graph shows the fork. - A tag called
v1.0on the final commit ofmain.
GitHub requirements:
- A public repository named
recipe-bookwith your work pushed. - One pull request, created from a branch called
add-ginger-note, with a title and a description in the format "What, Why, How to check". - The pull request is merged, and its branch is deleted on GitHub and locally.
Example of what a correct result looks like
git log --oneline --graph --decorate on your final main should look like this (your IDs will differ):
* 829d30e (HEAD -> main, tag: v1.0, origin/main) Merge add-ginger-note
|\
| * acd0e06 Add ginger tip to chai
|/
* e8e7971 Merge add-index
|\
| * 6184af2 List recipes in README
* | ea3e8aa Describe the book in README
|/
* 05cd3ee Add poha recipe
* a9b4968 Add masala chai recipe
* 60a34ae Add README and .gitignoreand git ls-files prints exactly:
.gitignore
README.md
chai.md
poha.mdwhile ls -a still shows .env, debug.log and notes-private. Finally git status -sb should say ## main...origin/main with nothing after it.
The graph shows two diamonds: one from the deliberate conflict, one from the --no-ff merge. If your graph is a straight line, you missed one of the history requirements.
Requirements checklist
Copy this list into your notes and tick items as you go.
- Git is installed and
git config --global user.nameanduser.emailare set - Default branch is
main(git config --global init.defaultBranch main) -
recipe-bookfolder is a Git repository - First commit contains
.gitignoreandREADME.md -
.env,debug.logandnotes-private/ideas.txtexist but are untracked and not shown bygit status -
chai.mdandpoha.mdeach added in their own commit - Branch
add-indexchangesREADME.mdto add a recipe list -
mainchanges the same line ofREADME.mdbefore the merge, creating a conflict - Conflict resolved by hand with all markers removed and both ideas kept
- Branch
add-ginger-notemerged withgit merge --no-ff - Remote
originadded andmainpushed with-u - Pull request opened, merged and the branch deleted on GitHub and locally
- Tag
v1.0created and pushed -
git statussays the working tree is clean at the end - Every commit message is imperative and under 50 characters
Grading rubric
Total: 100 points. Use these descriptions to grade yourself honestly, or ask a friend to grade you.
A 60 percent solution (passes):
- The repository exists on GitHub with the four files and a history of at least six commits.
.envanddebug.logare not tracked.- A merge was done, but the conflict was avoided (for example, different lines were edited), or the PR was created but not merged.
- Some messages are weak, for example "update README".
An 80 percent solution (good):
- Everything in the 60 percent list, plus the conflict is real and resolved correctly, with both ideas kept and no markers left in the file.
- Messages are imperative and clear.
- The pull request exists, was merged, and has a description.
- The
.gitignorewas committed first, so no private file ever entered history. You can prove it withgit log --all --oneline -- .env, which prints nothing. - Missing: the tag, or the
--no-ffmerge, or the clean-up of branches.
A 100 percent solution (job-ready habits):
- All checklist items pass, and the log graph matches the example shape.
- Commit messages follow the rules from lesson 3 (imperative, under 50 characters, one purpose each).
- The PR description follows the What, Why, How-to-check format.
- Branches are deleted locally and on GitHub;
git branch -ashows onlymain. - You wrote a short
README.mdsection called "How this repo was built" listing the commands you used for the conflict, in your own words. - You can explain every command you used, which you will test by answering the questions at the end.
Step-by-step hints
Open each hint only when you are stuck. Each one gives direction without writing the whole answer.
Hint 1: Setup and the first commit
Create the folder, run git init, and check git status. Create the private files first if you like, and the .gitignore before you run any git add. Write the three patterns, one per line: .env, *.log, notes-private/. Stage .gitignore and README.md by name (not with git add .), commit, then run git status -s and confirm the private files are not listed.
Hint 2: Recipes
Make chai.md and commit it alone. Make poha.md and commit it alone. Two commits, two stories. A printf with \n is the quickest way to write several lines from the terminal, or use your editor.
Hint 3: Connect to GitHub
Create an empty public repository on GitHub with no README. Copy its URL. Run git remote add origin <url>, then git push -u origin main. Check the files appear on the web page.
Hint 4: Causing the conflict
You need the same line changed on two branches. Create add-index with git switch -c add-index, and change the description line of README.md while adding the recipe list. Commit. Switch back to main and change only the description line to something different. Commit. Run git merge add-index and read CONFLICT. Run git status and open the file.
Hint 5: Resolving it
Between <<<<<<< HEAD and ======= is the version on main. Between ======= and >>>>>>> add-index is the version from the branch. Keep the description from main and the bullet list from the branch, and delete all three marker lines. Then git add README.md and git commit -m "Merge add-index". If you panic, git merge --abort returns to the state before the merge and you can try again.
Hint 6: The no-fast-forward merge
Create add-ginger-note from main, add a line to chai.md, and commit. Push it with git push -u origin add-ginger-note and open a pull request on GitHub for practice. For the local history requirement, you can merge on the command line instead: git switch main, then git merge --no-ff add-ginger-note. If you merge on GitHub, press the Merge pull request button (which makes a merge commit) and run git pull locally. Either way the graph shows a fork and join.
Hint 7: Clean-up and tag
Delete the branch locally with git branch -d add-ginger-note and on the remote with git push origin --delete add-ginger-note. Create the tag with git tag v1.0 and push it with git push origin v1.0. Check that git branch -a lists only main and the remote-tracking origin/main.
Hint 8: Prove the history is clean
Run git log --oneline --graph --decorate, git ls-files, git status -sb and git log --all --oneline -- .env. Compare with the example. The last command must print nothing.
Reference solution
Try the project first. Then compare with this complete command sequence. It was run on Git 2.53 and produced the output shown earlier.
Reference solution: all commands
Set up once (skip if already done):
git config --global user.name "Asha Rao"
git config --global user.email "asha@example.com"
git config --global init.defaultBranch mainCreate the project and the private files:
mkdir recipe-book
cd recipe-book
git init
echo "API_KEY=not-a-real-key" > .env
echo "something went wrong" > debug.log
mkdir notes-private
echo "try a mango lassi" > notes-private/ideas.txtFirst commit: ignore rules and README, nothing private.
printf '.env\n*.log\nnotes-private/\n' > .gitignore
printf '# Recipe Book\n\nMy favourite recipes.\n' > README.md
git add .gitignore README.md
git commit -m "Add README and .gitignore"
git status -sgit status -s prints nothing, because the three private items are ignored.
Recipes, one commit each:
printf '# Masala Chai\n\n- 2 cups water\n- 1 cup milk\n- 2 tsp tea leaves\n' > chai.md
git add chai.md
git commit -m "Add masala chai recipe"
printf '# Poha\n\n- 1 cup flattened rice\n- 1 onion\n' > poha.md
git add poha.md
git commit -m "Add poha recipe"Connect to GitHub (replace the URL with yours) and push:
git remote add origin https://github.com/yourname/recipe-book.git
git push -u origin mainThe conflict, on purpose. Branch first:
git switch -c add-index
printf '# Recipe Book\n\nMy favourite recipes.\n\n- [Chai](chai.md)\n- [Poha](poha.md)\n' > README.md
git commit -am "List recipes in README"Back on main, change the same description line differently:
git switch main
printf '# Recipe Book\n\nMy favourite recipes, tested at home.\n' > README.md
git commit -am "Describe the book in README"Merge and meet the conflict:
git merge add-indexAuto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.git status -s shows UU README.md. The file now reads:
# Recipe Book
<<<<<<< HEAD
My favourite recipes, tested at home.
=======
My favourite recipes.
- [Chai](chai.md)
- [Poha](poha.md)
>>>>>>> add-indexResolve by keeping the new description and the list:
printf '# Recipe Book\n\nMy favourite recipes, tested at home.\n\n- [Chai](chai.md)\n- [Poha](poha.md)\n' > README.md
git add README.md
git commit -m "Merge add-index"
git pushThe feature branch, pull request and no-fast-forward merge:
git switch -c add-ginger-note
printf '\nTip: crush the ginger before boiling.\n' >> chai.md
git commit -am "Add ginger tip to chai"
git push -u origin add-ginger-noteOpen a pull request on GitHub from add-ginger-note into main. Use a description like:
What: Adds a ginger tip to the chai recipe.
Why: Crushed ginger gives a stronger flavour.
How to check: Open chai.md and read the last line.Merge the PR on GitHub (or locally with git merge --no-ff add-ginger-note -m "Merge add-ginger-note" followed by git push). Then update, clean up and tag:
git switch main
git pull
git branch -d add-ginger-note
git push origin --delete add-ginger-note
git tag v1.0
git push origin v1.0Check the result:
git log --oneline --graph --decorate
git ls-files
git status -sb
git log --all --oneline -- .envThe last command prints nothing, which proves the key was never committed. If you merged on GitHub rather than locally, git pull creates the same shape of graph, and the branch deletion may already be done by GitHub: skip the delete command if you see an error that the remote ref does not exist.
Extension challenges
Finished early? Try these, in order of difficulty.
- Revert a mistake. Add a bad commit
Add broken link, push it, and then undo it safely withgit revert. Explain in the PR why you did not use--amend. - Simulate a teammate. Clone the repository into a second folder, make a commit there and push it. In the first folder, make a different commit, try to push, read the rejection, and fix it with
git pull --no-rebase. - Add a CONTRIBUTING file. In a new branch, write
CONTRIBUTING.mdwith three rules for good commit messages, and open a PR. - Find the author. Use
git blame README.mdto find which commit introduced the bullet list.
Questions to answer in your own words
Answer these in a text file or aloud. If you cannot, reread the matching lesson.
- Why did you commit
.gitignorebefore the private files could be staged? - In the conflict, which side was
HEAD, and why? - What does
git push -uremember, and why do you not need-uthe second time? - What would have happened if you had run
git push --forceafter a rejected push in the extension challenge? - Why does the
git log --all --oneline -- .envcommand print nothing, and what would it print if you had committed.envand deleted it later?
Answers to the questions
- Ignore rules apply only to untracked files. Once a file is committed, adding it to
.gitignoredoes not remove it, and the old copy stays in history. Committing the rules first means the private files were never tracked. HEADwasmain, the branch you stood on when you rangit merge add-index. The text after=======came from the branch you merged in.-urecords that localmaintracksorigin/main, so later plaingit pushandgit pullknow where to go. It is stored in the repository's config, so it is needed only once per branch.- Force would overwrite the remote
mainwith your copy and delete the teammate's commit from GitHub. The right fix isgit pullthengit push. - The file was never tracked, so no commit touches it. If it had been committed and deleted later, the command would list both the commit that added it and the commit that removed it, proving the secret remains in history.
Summary of the module
- Version control records history so you can undo, trace and collaborate; Git is the local tool, GitHub the hosting service.
- Install Git, set your name and email, and set
mainas the default branch. - Move changes working directory, staging area, repository; write imperative messages; ignore secrets with
.gitignore. - Branches are pointers; merges are fast-forward or three-way; conflicts are resolved by editing markers,
git add,git commit. - Push and pull connect your repo to a remote; a rejected push means pull first; pull requests and forks are how teams and open source collaborate.