List of exercises
Summary
Basics:
Branching and merging:
Conflict resolution:
Inspecting history:
Using the Git staging area:
Undoing and recovering:
Interrupted work:
Full list
This is a list of all exercises and solutions in this lesson, mainly as a reference for helpers and instructors. This list is automatically generated from all of the other pages in the lesson. Any single teaching event will probably cover only a subset of these, depending on their interests.
Motivation
Solution
Giving a version to a collaborator and merging changes later with own changes sounds like lots of work.
What if you discover a bug and want to know since when the bug existed?
Basics
Basic-1: Record changes
Add 1/2 onion to ingredients.txt
and also the instruction
to “enjoy!” to instructions.txt
.
After modifying the files, do not stage the changes yet (do not git add
yet).
When you are done editing the files, try git diff
:
$ git diff
You will see (can you identify in there the two added lines?):
diff --git a/ingredients.txt b/ingredients.txt
index 4422a31..ba8854f 100644
--- a/ingredients.txt
+++ b/ingredients.txt
@@ -2,3 +2,4 @@
* 1 chili
* 1 lime
* 2 tsp salt
+* 1/2 onion
diff --git a/instructions.txt b/instructions.txt
index 7811273..2b11074 100644
--- a/instructions.txt
+++ b/instructions.txt
@@ -4,3 +4,4 @@
* squeeze lime
* add salt
* and mix well
+* enjoy!
Now first stage and commit each change separately (what happens when we leave out the -m
flag?):
$ git add ingredients.txt
$ git commit -m "add half an onion"
$ git add instructions.txt
$ git commit # <-- we have left out -m "..."
When you leave out the -m
flag, Git should open an editor where you can edit
your commit message. This message will be associated and stored with the
changes you made. This message is your chance to explain what you’ve done and
convince others (and your future self) that the changes you made were
justified. Write a message and save and close the file.
When you are done committing the changes, experiment with these commands:
$ git log
$ git log --stat
$ git log --oneline
We can make modifications to a file by clicking on the file and then the pen symbol:
Our goal is to arrive at two new commits:
“add half an onion” (modifying
ingredients.txt
)“don’t forget to enjoy” (modifying
instructions.txt
)
When you are done committing the changes, try to browse commit history on GitHub.
(optional) Basic-2: Comparing and showing commits
Have a look at specific commits with
git show HASH
.Inspect differences between commit hashes with
git diff HASH1 HASH2
.
Have a look at specific commits.
Inspect differences between commits.
Use the screenshots under “Git history and log”.
(optional) Basic-3: Visual diff tools
This exercise is only relevant for the command line. In the browser, the preview is already side-by-side and “visual”.
Make further modifications and experiment with
git difftool
(requires installing one of the visual diff tools):
On Windows or Linux:
$ git difftool --tool=meld HASH
On macOS:
$ git difftool --tool=opendiff HASH
You probably want to use the same visual diff tool every time and you can configure Git for that:
$ git config --global diff.tool meld
(optional) Basic-4: Browser and command line
You have noticed that it is possible to work either in the command line or in the browser. It could help to deepen the understanding trying to do the above steps in both.
If you have managed to do the above in the command line, try now in the browser.
If you got stuck in the command line and move to the browser, try now to trouble-shoot the command line Git.
Basic-5: Test your understanding
Which command(s) below would save the changes of myfile.txt
to an existing local Git repository?
$ git commit -m "my recent changes"
$ git init myfile.txt $ git commit -m "my recent changes"
$ git add myfile.txt $ git commit -m "my recent changes"
$ git commit -m myfile.txt "my recent changes"
Solution
Would only create a commit if files have already been staged.
Would try to create a new repository in a folder “myfile.txt”.
Is correct: first add the file to the staging area, then commit.
Would try to commit a file “my recent changes” with the message myfile.txt.
Branching and merging
Branch-1: Create and commit to branches
In this exercise, you will create another new branch and few more commits. We will use this in the next section, to practice merging. The goal of the exercise is to end up with 3 branches.
Change to the branch
main
.Create another branch called
less-salt
.Note! makes sure you are on main branch when you create the
less-salt
branch.A safer way would be to explicitly mention to create from the main branch as shown below:
$ git branch less-salt main
Switch to the
less-salt
branch.On the
less-salt
branch reduce the amount of salt.Commit your changes to the
less-salt
branch.
Use the same commands as we used above.
We now have three branches (in this case HEAD
points to less-salt
):
$ git branch
experiment
* less-salt
main
$ git graph
* bf28166 (HEAD -> less-salt) reduce amount of salt
| * bcb8b78 (experiment) maybe little bit less cilantro
| * f6ec7b7 let us try with some cilantro
|/
* e7cf023 (main) don't forget to enjoy
* 79161b6 add half an onion
* a3394e3 adding README
* 3696246 adding instructions
* f146d25 adding ingredients
Here is a graphical representation of what we have created:
Now switch to
main
.In a new commit, improve the
README.md
file (we added the word “Guacamole”):# Guacamole recipe This is an exercise repository.
Now you should have this situation:
$ git graph
* b4af65b (HEAD -> main) improve the documentation
| * bf28166 (less-salt) reduce amount of salt
|/
| * bcb8b78 (experiment) maybe little bit less cilantro
| * f6ec7b7 let us try with some cilantro
|/
* e7cf023 don't forget to enjoy
* 79161b6 add half an onion
* a3394e3 adding README
* 3696246 adding instructions
* f146d25 adding ingredients
And for comparison this is how it looks on GitHub.
Branch-2: Merge branches
Merge experiment
and less-salt
back into main
following the lesson below
until the point where we start deleting branches.
(optional) Branch-3: Perform a fast-forward merge
Create a new branch from
main
and switch to it.Create a couple of commits on the new branch (for instance edit
README.md
):Now switch to
main
.Merge the new branch to
main
.Examine the result with
git graph
.Have you expected the result? Discuss what you see.
Solution
You will see that in this case no merge commit was created and Git merged the two branches by moving (fast-forwarding) the “main” branch (label) three commits forward.
This was possible since one branch is the ancestor of the other and their developments did not diverge.
A merge that does not require any merge commit is a fast-forward merge.
(optional) Branch-4: Rebase a branch (instead of merge)
As an alternative to merging branches, one can also rebase branches. Rebasing means that the new commits are replayed on top of another branch (instead of creating an explicit merge commit). Note that rebasing changes history and should not be done on public commits!
Create a new branch, and make a couple of commits on it.
Switch back to
main
, and make a couple of commits on it.Inspect the situation with
git graph
.Now rebase the new branch on top of
main
by first switching to the new branch, and thengit rebase main
.Inspect again the situation with
git graph
. Notice that the commit hashes have changed - think about why!
Solution
You will notice two things:
History is now linear and does not contain merge commits.
All the commit hashes that were on the branch that got rebased, have changed. This also demonstrates that
git rebase
is a command that alters history. The commit history looks as if the rebased commits were all done after themain
commits.
Branch-5: Test your understanding
Which of the following combos (one or more) creates a new branch and makes a commit to it?
$ git branch new-branch $ git add file.txt $ git commit
$ git add file.txt $ git branch new-branch $ git switch new-branch $ git commit
$ git switch --create new-branch $ git add file.txt $ git commit
$ git switch new-branch $ git add file.txt $ git commit
Solution
Both 2 and 3 would do the job. Note that in 2 we first stage the file, and then create the
branch and commit to it. In 1 we create the branch but do not switch to it, while in 4 we
don’t give the --create
flag to git switch
to create the new branch.
Conflict resolution
Conflict-1: Create another conflict and resolve
In this exercise, we repeat almost exactly what we did above with a different ingredient.
Create two branches before making any modifications.
Again modify some ingredient on both branches.
Merge one, merge the other and observe a conflict, resolve the conflict and commit the merge.
What happens if you apply the same modification on both branches?
If you create a branch
like-avocados
, commit a change, then from this branch create another banchdislike-avocados
, commit again, and try to merge both branches intomain
you will not see a conflict. Can you explain, why it is different this time?
Solution
4: No conflict in this case if the change is the same.
5: No conflict in this case since in Git history one change happened after the other. The two changes are related and linked by Git history and one is a Git ancestor of the other. Git will assume that since we applied one change after the other, we meant this. There is nothing to resolve.
(optional) Conflict-2: Resolve a conflict when rebasing a branch
Create two branches where you anticipate a conflict.
Try to merge them and observe that indeed they conflict.
Abort the merge with
git merge --abort
.What do you expect will happen if you rebase one branch on top of the other? Do you anticipate a conflict? Try it out.
Solution
Yes, this will conflict. If it conflicts during a merge, it will also conflict
during rebase but the conflict resolution looks slightly different:
You still need to look for conflict markers but you tell Git that you resolved
a conflict with git add
and then you continue with git rebase --continue
.
Follow instructions that you get from the Git command line.
(optional) Conflict-3: Resolve a conflict using mergetool
Again create a conflict (for instance disagree on the number of avocados).
Stop at this stage:
Auto-merging ingredients.txt CONFLICT (content): Merge conflict in ingredients.txt Automatic merge failed; fix conflicts and then commit the result.
Instead of resolving the conflict manually, use a visual tool (requires installing one of the visual diff tools):
$ git mergetool
Your current branch is left, the branch you merge is right, result is in the middle.
After you are done, close and commit,
git add
is not needed when usinggit mergetool
.
If you have not instructed Git to avoid creating backups when using mergetool, then to be on the safe side there will be additional temporary files created. To remove those you can do a git clean after the merging.
To view what will be removed:
$ git clean -n
To remove:
$ git clean -f
To configure Git to avoid creating backups at all:
$ git config --global mergetool.keepBackup false
Inspecting history
History-1: Explore basic archaeology commands
Let us explore the value of these commands in an exercise. Future exercises do not depend on this, so it is OK if you do not complete it fully.
Exercise steps:
Clone this repository: https://github.com/networkx/networkx.git. Then step into the new directory and create an exercise branch from the networkx-2.6.3 tag/release:
$ git clone https://github.com/networkx/networkx.git $ cd networkx $ git switch --create exercise networkx-2.6.3
On old Git versions which do not know the
switch
command (before 2.23), you need to use this instead:$ git checkout -b exercise networkx-2.6.3
Then using the above toolbox try to:
Find the code line which contains
"Logic error in degree_correlation"
.Find out when this line was last modified or added. Find the actual commit which modified that line.
Inspect that commit with
git show
.Create a branch pointing to the past when that commit was created to be able to browse and use the code as it was back then.
How would you bring the code to the version of the code right before that line was last modified?
Solution
We provide here a solution for the command line but we also encourage you to try to solve this in the browser.
We use
git grep
:$ git grep "Logic error in degree_correlation"
This gives the output:
networkx/algorithms/threshold.py: print("Logic error in degree_correlation", i, rdi)
Maybe you also want to know the line number:
$ git grep -n "Logic error in degree_correlation"
We use
git annotate
:$ git annotate networkx/algorithms/threshold.py
Then search for “Logic error” by typing “/Logic error” followed by Enter. The last commit that modified it was
90544b4fa
(unless that line changed since).We use
git show
:$ git show 90544b4fa
Create a branch pointing to that commit (here we called the branch “past-code”):
$ git branch past-code 90544b4fa
This is a compact way to access the first parent of
90544b4fa
(here we called the branch “just-before”):$ git switch --create just-before 90544b4fa~1
(optional) History-2: Use git bisect to find the bad commit
In this exercise, we use git bisect
on an example repository. It
is OK if you do not complete this exercise fully.
Begin by cloning https://github.com/coderefinery/git-bisect-exercise.
Motivation
The motivation for this exercise is to be able to do archaeology with Git on a source code where the bug is difficult to see visually. Finding the offending commit is often more than half the debugging.
Background
The script get_pi.py
approximates pi using terms of the Nilakantha series. It
should produce 3.14 but it does not. The script broke at some point and
produces 3.57 using the last commit:
$ python get_pi.py
3.57
At some point within the 500 first commits, an error was introduced. The only thing we know is that the first commit worked correctly.
Your task
Clone this repository and use
git bisect
to find the commit which broke the computation (solution - spoiler alert!).Once you have found the offending commit, also practice navigating to the last good commit.
Bonus exercise: Write a script that checks for a correct result and use
git bisect run
to find the offending commit automatically (solution - spoiler alert!).
Hints
Finding the first commit:
$ git log --oneline | tail -n 1
How to navigate to the parent of a commit with hash SOMEHASH:
$ git switch --create BRANCHNAME SOMEHASH~1
Instead of a tilde you can also use this:
$ git switch --create BRANCHNAME SOMEHASH^
Using the Git staging area
Staging-1: Perform an interactive commit
One option to help us create nice logical commits is to stage interactively
with git commit --patch
:
Make two changes in
instructions.txt
, at the top and bottom of the file. Make sure that they are separated by at least several unmodified lines.Run
git commit --patch
. Using the keystrokes above, commit one of the changes.Do it again for the other change.
When you’re done, inspect the situation with
git log
,git status
,git diff
andgit diff --staged
.When would this be useful?
Solution
This can be useful if you have several modification in a file (or several files) but you decide that it would be beneficial to save them as two (or more) separate commits.
Staging-2: Use the staging area to make a commit in two steps
In your recipe example, make two different changes to
ingredients.txt
andinstructions.txt
which do not go together.Use
git add
to stage one of the changes.Use
git status
to see what’s going on, and usegit diff
andgit diff --staged
to see the changes.Feel some regret and unstage the staged change.
Undoing and recovering
Undoing-1: Revert a commit
Create a commit (commit A).
Revert the commit with
git revert
(commit B).Inspect the history with
git log --oneline
.Now try
git show
on both the reverted (commit A) and the newly created commit (commit B).
Undoing-2: Modify a previous commit
Make an incomplete change to the recipe or a typo in your change,
git add
andgit commit
the incomplete/unsatisfactory change.Inspect the unsatisfactory but committed change with
git show
. Remember or write down the commit hash.Now complete/fix the change but instead of creating a new commit, add the correction to the previous commit with
git add
, followed bygit commit --amend
. What changed?
Solution
One thing that has changed now is the commit hash. Modifying the previous commit has changed the history. This is OK to do on commits that other people don’t depend on yet.
Undoing-3: Destroy our experimentation in this episode
After we have experimented with reverts and amending, let us destroy all of that and get our repositories to a similar state.
First, we will look at our history (
git log
/git graph
) and find the last commitHASH
before our tests.Then, we will
git reset --hard HASH
to that.Then,
git graph
again to see what happened.
$ git log --oneline
d3fc63a (HEAD -> main) Revert "not sure this is a good idea"
e02efcd not sure this is a good idea
b4af65b improve the documentation
e7cf023 don't forget to enjoy
79161b6 add half an onion
a3394e3 adding README
3696246 adding instructions
f146d25 adding ingredients
$ git reset --hard b4af65b
HEAD is now at b4af65b improve the documentation
$ git log --oneline
b4af65b (HEAD -> main) improve the documentation
e7cf023 don't forget to enjoy
79161b6 add half an onion
a3394e3 adding README
3696246 adding instructions
f146d25 adding ingredients
Undoing-4: Test your understanding
What happens if you accidentally remove a tracked file with
git rm
, is it gone forever?Is it OK to modify commits that nobody has seen yet?
What situations would justify to modify the Git history and possibly remove commits?
Solution
It is not gone forever since
git rm
creates a new commit. You can revert the commit to get the file back.If you haven’t shared your commits with anyone it can be alright to modify them.
If you have shared your commits with others (e.g. pushed them to GitHub), only extraordinary conditions would justify modifying history. For example to remove sensitive or secret information.
Interrupted work
Interrupted-1: Stash some uncommitted work
Make a change.
Check status/diff, stash the change with
git stash
, check status/diff again.Make a separate, unrelated change which doesn’t touch the same lines. Commit this change.
Pop off the stash you saved with
git stash pop
, and check status/diff.Optional: Do the same but stash twice. Also check
git stash list
. Can you pop the stashes in the opposite order?Advanced: What happens if stashes conflict with other changes? Make a change and stash it. Modify the same line or one right above or below. Pop the stash back. Resolve the conflict. Note there is no extra commit.
Advanced: what does
git graph
show when you have something stashed?
Solution
5: Yes you can. With git stash pop INDEX
you can decie which stash
index to pop.
6: In this case Git will ask us to resolve the conflict the same way when resolving conflicts between two branches.
7: It shows an additional commit hash with refs/stash
.