List of exercises

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

In motivation.md:

Copy and browse an existing project

In browsing.md:

Exercise: Browsing an existing project (25 min)

Browse the recipe-book project (introduced above) and explore commits and branches. Take notes and prepare questions. The hints are for the GitHub path in the browser.

  1. Browse the commit history: Are commit messages understandable? (Hint: “Commit history”, the timeline symbol, above the file list)

  2. Compare the commit history with the network graph (“Insights” -> “Network”). Can you find the branches?

  3. How can you find out when a recipe was last modified?

  4. How many changes did the Guacamole recipe receive (you find it under “sides”)? Try to click on some of the commits to see what changed. (Hint: “History” in the view of a single file)

  5. Which recipes include the ingredient “salt”? (Hint: the GitHub search. From the repository view, it should offer the filter “repo:USER/recipe-book” by default. What if you add a search term?)

  6. In the Guacamole recipe, find out who modified each line last and when (click on file, then click “Blame” button). Find out who added the cilantro and in which commit. (Hint: “Blame” view in the file view)

  7. Can you use these recipes yourself? Are you allowed to share modifications? (Hint: look for a license file)

  8. Browse issues and pull requests in the upstream repository (the repository you forked from). Any idea what these might be good for? (Hint: tabs in the repository view)

Committing changes

In commits.md:

Exercise: Practice creating commits and branches (20 min)

  1. Make sure that you now work on your fork of the recipe-book repository (USER/recipe-book, not coderefinery/recipe-book)

  2. First create a new branch and then add a recipe to the branch and commit the change.

  3. In a new commit, modify the recipe you just added.

  4. Switch to the main branch and modify a recipe there.

  5. Browse the network and locate the commits that you just created (“Insights” -> “Network”).

  6. Compare the branch that you created with the main branch. Can you find an easy way to see the differences?

  7. Can you find a way to compare versions between two arbitrary commits in the repository?

  8. Try to rename the branch that you created and then browse the network again.

  9. Try to create a tag for one of the commits that you created (on GitHub, create a “release”).

Merging changes and contributing to the project

In merging.md:

Exercise: Merging branches with pull requests (20 min)

We assume that in the previous exercise you have created a new branch with a recipe. In our previous example, it is called new-recipe. If not, create it first and add a recipe to your new branch, see Committing changes.

We provide basic hints. You should refer to the solution as needed.

  1. Navigate to your branch from the previous episode (Hint: the same branch view we used last time).

  2. Begin the pull request process. (Hint: There is a “Contribute” button in the branch view).

  3. Add or modify the pull request title and description, and verify the other data. In the pull request verify the target repository and the target branch. Make sure that you are merging within your own repository. GitHub: By default, it will offer to make the change to the upstream repository, coderefinery. You should change this, you shouldn’t contribute your test recipe upstream yet. Where it says base repository, select your own user’s repository.

  4. Create the pull request by clicking “Create pull request”. Browse the network view to see if anything has changed yet.

  5. Merge the pull request, or if you are not on GitHub you can merge the branch locally. Browse the network again. What has changed?

  6. Find out which branches are merged and thus safe to delete. Then remove them and verify that the commits are still there, only the branch labels are gone. (Hint: you can delete branches that have been merged into main).

  7. Optional: Try to create a new branch with a new change, then open a pull request but towards the central repository. We will later merge few of those. (Hint: this is mostly the same as above, for the GitHub path. But, you set the base repository as CodeRefinery. You might need to compare across forks.)

In merging.md:

Exercise

  1. Switch to the main branch that you want to merge the other branch into. (Note that this is the other way around from the GitHub path).

Then:

  1. Merge the other branch into main (which is then your current branch).

  2. Find out which branches are merged and thus safe to delete. Then remove them and verify that the commits are still there, only the branch labels are gone. (Hint: you can delete branches that have been merged into main).

  3. (optional, advanced) Try to create a new branch, and make a GitHub pull request with your recipe, and contribute it to our upstream repository. This is very complex right now since your change has to get to GitHub, and we haven’t shown that yet. We don’t give a solution for this.

Cloning a Git repository and working locally

In local-workflow.md:

Exercise: Cloning a Git repository and working locally (25 min)

  1. Configure Git command line and editor if you haven’t done that already.

  2. Decide which repository you want to clone: your fork or the original repository? Both will work for this exercise. Then, clone the recipe book.

  3. Create a new branch.

  4. Make a commit on your new branch.

  5. Switch back to the main branch and create one or two commits there.

  6. Merge the new branch into main.

  7. Compare the graph locally and on GitHub and observe that the changes only exist locally on your computer.

  8. Where are the remote branches? Practice how you can see all remote branches also locally and how you can fetch them and make local changes to them.

Inspecting history

In archaeology.md:

Exercise: Explore basic archaeology commands (20 min)

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:

  • Make sure you are not inside another Git repository when running this exercise. If you are, first step “outside” of it. We want to avoid creating a Git repository inside another Git repository.

    You can check if you are inside a Git repository with:

    $ git status
    
    fatal: not a git repository (or any of the parent directories): .git
    

    You want to see the above message which tells us that this is not a Git repository.

  • Clone this repository: https://github.com/networkx/networkx.git.

    $ git clone https://github.com/networkx/networkx.git
    
  • Then let us all make sure we are working on a well-defined version of the repository.

    Step into the new directory and create an exercise branch from the networkx-2.6.3 tag/release:

    $ 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:

  1. Find the code line which contains "Logic error in degree_correlation".

  2. Find out when this line was last modified or added. Find the actual commit which modified that line.

  3. Inspect that commit with git show.

  4. 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.

  5. How would you bring the code to the version of the code right before that line was last modified?

In archaeology.md:

(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^

How to turn your project to a Git repo and share it

In sharing.md:

Exercise: Turn your project to a Git repo and share it (25 min)

  1. Create a new directory called myproject with one or few files in it. This represents our own project. It is not yet a Git repository. You can try that with your own project or use a simple placeholder example.

  2. Turn this new directory into a Git repository.

  3. Share this repository on GitHub (or GitLab, since it really works the same).

We offer three different paths of how to do this exercise.

  • Via GitHub web interface: easy and can be a good starting point if you are completely new to Git.

  • VS Code is quite easy, since VS Code can offer to create the GitHub repositories for you.

  • Command line: you need to create the repository on GitHub and link it yourself.

Create an repository on GitHub

First log into GitHub, then follow the screenshots and descriptions below.

Screenshot on GitHub before a new repository form is opened

Click on the “plus” symbol on top right, then on “New repository”.

Then:

Screenshot on GitHub just before a new repository is created

Choose a repository name, add a short description, and in this case make sure to check “Add a README file”. Finally “Create repository”.

Upload your files

Now that the repository is created, you can upload your files:

Screenshot on GitHub just before uploading files

Click on the “+” symbol and then on “Upload files”.

Basics

In basics.md:

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

In basics.md:

(optional) Basic-2: Comparing and showing commits

  1. Have a look at specific commits with git show HASH.

  2. Inspect differences between commit hashes with git diff HASH1 HASH2.

In basics.md:

(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
Git difftool using meld

Git difftool using meld.

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

In basics.md:

(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.

In basics.md:

Basic-5: Test your understanding

Which command(s) below would save the changes of myfile.txt to an existing local Git repository?

  1. $ git commit -m "my recent changes"
    
  2. $ git init myfile.txt
    $ git commit -m "my recent changes"
    
  3. $ git add myfile.txt
    $ git commit -m "my recent changes"
    
  4. $ git commit -m myfile.txt "my recent changes"
    

Branching and merging

In branches.md:

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:

../_images/git-branch-2.svg
  • 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
../_images/git-branch-3.svg

And for comparison this is how it looks on GitHub.

In branches.md:

Branch-2: Merge branches

Merge experiment and less-salt back into main following the lesson below until the point where we start deleting branches.

In branches.md:

(optional) Branch-3: Perform a fast-forward merge

  1. Create a new branch from main and switch to it.

  2. Create a couple of commits on the new branch (for instance edit README.md):

    ../_images/git-pre-ff.svg
  3. Now switch to main.

  4. Merge the new branch to main.

  5. Examine the result with git graph.

  6. Have you expected the result? Discuss what you see.

In branches.md:

(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!

  1. Create a new branch, and make a couple of commits on it.

  2. Switch back to main, and make a couple of commits on it.

  3. Inspect the situation with git graph.

  4. Now rebase the new branch on top of main by first switching to the new branch, and then git rebase main.

  5. Inspect again the situation with git graph. Notice that the commit hashes have changed - think about why!

In branches.md:

Branch-5: Test your understanding

Which of the following combos (one or more) creates a new branch and makes a commit to it?

  1. $ git branch new-branch
    $ git add file.txt
    $ git commit
    
  2. $ git add file.txt
    $ git branch new-branch
    $ git switch new-branch
    $ git commit
    
  3. $ git switch --create new-branch
    $ git add file.txt
    $ git commit
    
  4. $ git switch new-branch
    $ git add file.txt
    $ git commit
    

Conflict resolution

In conflicts.md:

Conflict-1: Create another conflict and resolve

In this exercise, we repeat almost exactly what we did above with a different ingredient.

  1. Create two branches before making any modifications.

  2. Again modify some ingredient on both branches.

  3. Merge one, merge the other and observe a conflict, resolve the conflict and commit the merge.

  4. What happens if you apply the same modification on both branches?

  5. If you create a branch like-avocados, commit a change, then from this branch create another banch dislike-avocados, commit again, and try to merge both branches into main you will not see a conflict. Can you explain, why it is different this time?

In conflicts.md:

(optional) Conflict-2: Resolve a conflict when rebasing a branch

  1. Create two branches where you anticipate a conflict.

  2. Try to merge them and observe that indeed they conflict.

  3. Abort the merge with git merge --abort.

  4. What do you expect will happen if you rebase one branch on top of the other? Do you anticipate a conflict? Try it out.

In conflicts.md:

(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
    
    Conflict resolution using 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 using git 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

Using the Git staging area

In staging-area.md:

Staging-1: Perform an interactive commit

One option to help us create nice logical commits is to stage interactively with git commit --patch:

  1. 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.

  2. Run git commit --patch. Using the keystrokes above, commit one of the changes.

  3. Do it again for the other change.

  4. When you’re done, inspect the situation with git log, git status, git diff and git diff --staged.

  5. When would this be useful?

In staging-area.md:

Staging-2: Use the staging area to make a commit in two steps

  1. In your recipe example, make two different changes to ingredients.txt and instructions.txt which do not go together.

  2. Use git add to stage one of the changes.

  3. Use git status to see what’s going on, and use git diff and git diff --staged to see the changes.

  4. Feel some regret and unstage the staged change.

Undoing and recovering

In recovering.md:

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).

In recovering.md:

Undoing-2: Modify a previous commit

  1. Make an incomplete change to the recipe or a typo in your change, git add and git commit the incomplete/unsatisfactory change.

  2. Inspect the unsatisfactory but committed change with git show. Remember or write down the commit hash.

  3. Now complete/fix the change but instead of creating a new commit, add the correction to the previous commit with git add, followed by git commit --amend. What changed?

In recovering.md:

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 commit HASH 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

In recovering.md:

Undoing-4: Test your understanding

  1. What happens if you accidentally remove a tracked file with git rm, is it gone forever?

  2. Is it OK to modify commits that nobody has seen yet?

  3. What situations would justify to modify the Git history and possibly remove commits?

Interrupted work

In interrupted.md:

Interrupted-1: Stash some uncommitted work

  1. Make a change.

  2. Check status/diff, stash the change with git stash, check status/diff again.

  3. Make a separate, unrelated change which doesn’t touch the same lines. Commit this change.

  4. Pop off the stash you saved with git stash pop, and check status/diff.

  5. Optional: Do the same but stash twice. Also check git stash list. Can you pop the stashes in the opposite order?

  6. 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.

  7. Advanced: what does git graph show when you have something stashed?