Version Control with Git

Authors
Dr. Atle Rimehaug | Dr. Ole Bialas | Dr. Nicholas Del Grosso | Dr. Sangeetha Nandakumar

Writing code is often not a linear process, involving exploration, tinkering, and trial-and-error. Without the right tools, this can result in a messy codebase with multiple, slightly modified versions of the same code, making it hard to understand, maintain and reproduce the project. Version control systems provide a solution for this: They record snapshots of your project at different points in time, which allows you to see what changed and why and to revert to previous versions of your code if needed.

Git is a distributed version control system and it is by far the most popular one with an adoption rate of over 90% in the software industry. In this lesson, you’ll learn the basics of Git: how to track changes, view the project’s history and restore old versions.

The materials feature two different ways of using git: the terminal and VSCode’s graphical user interface (GUI). It is recommended that you test out both methods because they each have their distinct advantages: The GUI provides a convenient interface that makes it easier to integrate Git into your workflow while the terminal allows you to use more advanced functionalities of Git that are not implemented in the GUI.

If you haven’t installed Git already, go to the website , download the installer for your operating system and follow the instructions (there are a lot of options in the installation - you can accept the defaults). Then run the following commands in your terminal (replacing the text in quotations with your actual name and mail) so Git can associate your commits with your identity.

git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

Section 1: Initializing a Repository and Adding Files

Background

At the beginning of every project, we initialize a new Git repository using the git init command. This will create the .git folder (which may be hidden, depending on your editor configuration) where Git stores the history of the project. Once initialized, Git will watch for any new files or changes to existing files in the working directory. When we use the git status command, Git prints a list of all current changes that have not yet been checked into the project’s history. In this section, we are going to use the git add command which stages files. Staging means that a file is marked as ready to be committed (more on what this means in the next section).

Exercises

In the following exercises you’ll create a new repository, add some files and stage them for committing. Here are the commands you need to know as well as the equivalent instructions for the VSCode Git GUI:

Code Description VS Code GUI
mkdir my_dir/ Create a new directory my_dir/ N/A
cd my_dir/ Change the current directory to my_dir/ N/A
cd ../ Move to the parent of the current directory N/A
git init Initialize a new Git repository in the current directory Click Source Control (Ctrl + Shift + G) → Click “Initialize Repository”
git status Show the status of changes in the working directory Click Source Control (Ctrl + Shift + G) → See the file changes
git add file1.txt Stage file1.txt for commit Click ➕ (plus) icon next to the file in Source Control
git add . Stage all modified and new files Click ➕ (plus) icon next to each file OR Click “Stage All Changes”
git reset file1.txt Unstage file1.txt Click ➖ (minus) icon next to the file to move it back to “Changes”

Exercise: Create a new empty folder and move there (either using your file explorer or the terminal commands mkdir and cd).

Solution
mkdir /tmp/new_repo
cd /tmp/new_repo
/tmp/new_repo

Exercise: Initialize a Git repository in this folder.

Solution
git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint: 	git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint: 	git branch -m <name>
Initialized empty Git repository in /tmp/new_repo/.git/

Exercise: Check the status of the repository using git status.

Solution
git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

Example: Create and stage experiment_1.txt.

# after creating experiment_1.txt:
git add experiment_1.txt

Exercise: Create experiment_2.txt and experiment_3.txt. Stage both the files together.

Solution
# after creating experiment_2.txt and experiment_3.txt:
git add .

Exercise: Unstage the experiment_3.txt and check the status.

Solution
git reset experiment_3.txt
git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   experiment_1.txt
	new file:   experiment_2.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	experiment_3.txt

Exercise: Delete experiment_3.txt and check the status.

Solution
# after deleting experiment_3.txt:
git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   experiment_1.txt
	new file:   experiment_2.txt

Section 2: Committing Changes and Viewing History

Background

The staged changes are still only temporary. To create a more permanent snapshot of your project, you need to save the staged changes using a commit. A commit records a snapshot of your project at a particular time described by a commit message. Essentially, your project will be a series of commits and the changes committed can be accessed via commit history.

Commits are saved in order, creating a history of changes in the project. You can see past commits using git log, which shows the commit hash, message, and time of each commit. In a typical workflow, you stage any change you make to the files in the temporary staging area and once you are happy with the changes you have made, you commit them. The points where you stage and commit are up to you.

The commit history also allows us to compare specific commits with git diff. The diff algorithm compares two files line-by-line to tell us exactly what has changed. We can select commits for git diff in two ways: either by using the commit hash which is the alphanumeric ID next to every commit in git log or by the position of the commit relative to HEAD (for example, HEAD~1 will one commit before HEAD, i.e. the second most recent commit). Note that the commit hashes are unique for every repository so the ones you’ll see in the exercises and solutions in this notebooks will not match the ones you’ll see on your machine.

Exercises

In the following exercises you will commit the staged changes and then edit and commit some more. You are also going to learn how to inspect the git log and compare specific commits with git diff. Here are the commands you need to know:

Code Description VS Code GUI
git add f1.txt f2.txt Stage specific files for commit Click ➕ (plus) icon next to the files in Source Control
git commit -m "Commit message" Commit staged files with a message Click ✔ (checkmark) icon, enter commit message, and press Enter
git add . Stage modifications of all files Click ➕ (plus) icon next Changes
git commit -am "Commit message" Stage and commit all modified files in one step Click “Stage All Changes”, then ✔ (checkmark) icon
git log View the full commit history of the repository N/A (Command-line only)
git log --oneline View a compact version of the commit history N/A (Command-line only)
git log -2 Display the last two commits N/A (Command-line only)
git diff d2dh5rt Compare the working directory to the commit with the hash d2dh5rt Click on the file in Source Control and check the inline diff
git diff HEAD~1 Compare the working directory to the second most recent commit Click on the file in Source Control and check the inline diff
git diff HEAD~3 HEAD~2 Compare the third and second most recent commits N/A (Command-line only)
git diff <commit-hash1> <commit-hash2> Compare two specific commits (the older commit goes first) N/A (Command-line only)

Exercise: Commit the staged files (experiment_1.txt and experiment_2.txt) with the message "create files".

Solution
git commit -m "create files"
[master (root-commit) 24a6eeb] add files
 2 files changed, 2 insertions(+)
 create mode 100644 experiment_1.txt
 create mode 100644 experiment_2.txt

Now, we’ll start making changes to files and commit those changes with git. If you don’t have Auto Save enabled, you have to save the file after making a change by tapping Ctrl + s or cmd + s (on a Mac keyboard) for git to be able detect the change.

If you have made a change to a file, like adding text to it, but haven’t saved it yet, there will be a white dot next to the filename in the tab in the editor (see red line in the screenshot below), the file while not show up under “Changes” in the source control, and when you check the status with git status in the terminal, it will say “nothing to commit, working tree clean” (see yellow line).

After saving the changes, the white dot next to the filename will disappear, the filename will show up under “Changes” in the source control, and when you check the status with git status, the output in the terminal will say that the file has been modified (see screenshot below).

You can enable Auto Save by clicking File in the top left corner of VS Code and then tick Auto Save.

Exercise: Add text to experiment_1.txt and commit with the message "add data to exp 1".

Solution
# after adding text to experiment_1.txt:
git add .
git commit -m "add data to exp 1"
[master 4c2c324] add data to exp 1
 1 file changed, 1 insertion(+)

Exercise: Add some text into experiment_2.txt, modify experiment_1.txt and commit the changes with a suitable commit message.

Solution
# after adding text to experiment_2.txt and modifying experiment_1.txt:
git add .
git commit -m "add data to exp 2 and modify exp 1"
[master c5ff83b] add data to exp 2 and modify exp 1
 2 files changed, 2 insertions(+), 2 deletions(-)

Exercise: Modify both the files. Stage and commit in one step.

Solution
# After modifying both files:
git commit -am "modify exp 1 and 2"
[master 350827e] add more data to exp 1 and 2
 2 files changed, 2 insertions(+)

Exercise: View the full commit history of the repository.

Solution
git log
commit 350827ebe0bf84d32eb44d846c40054dffa17c55 (HEAD -> master)
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:06 2026 +0200

    add more data to exp 1 and 2

commit c5ff83b293f4137b91ee2bd7dbf2620194a898f7
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:05 2026 +0200

    add data to exp 2 and modify exp 1

commit 4c2c324ff0c4b817301d729b815e802578e7011b
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:03 2026 +0200

    add data to exp 1

commit 24a6eebb9b4c362e64cb1467d156ffe870c99ee0
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:02 2026 +0200

    add files

Exercise: Check a compact version of the commit history.

Solution
git log --oneline
350827e (HEAD -> master) add more data to exp 1 and 2
c5ff83b add data to exp 2 and modify exp 1
4c2c324 add data to exp 1
24a6eeb add files

Exercise: Display the history of the last three commits.

Solution
git log -3
commit 350827ebe0bf84d32eb44d846c40054dffa17c55 (HEAD -> master)
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:06 2026 +0200

    add more data to exp 1 and 2

commit c5ff83b293f4137b91ee2bd7dbf2620194a898f7
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:05 2026 +0200

    add data to exp 2 and modify exp 1

commit 4c2c324ff0c4b817301d729b815e802578e7011b
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:03 2026 +0200

    add data to exp 1

Example: Compare the most recent commit and the working directory.

git diff HEAD~1 # alternatively, use the commit hash
diff --git a/experiment_1.txt b/experiment_1.txt
index 1984e41..4635490 100644
--- a/experiment_1.txt
+++ b/experiment_1.txt
@@ -1 +1,2 @@
 asjdhsajhd
+217336
diff --git a/experiment_2.txt b/experiment_2.txt
index 15eb2f7..4b09371 100644
--- a/experiment_2.txt
+++ b/experiment_2.txt
@@ -1,2 +1,3 @@
 This is the 2nd experiment
 1273672
+aodhahd

Exercise: Compare the differences between the third-most-recent commit and the working directory.

Solution
git diff HEAD~2 # alternatively, use the commit hash
diff --git a/experiment_1.txt b/experiment_1.txt
index b05b729..4635490 100644
--- a/experiment_1.txt
+++ b/experiment_1.txt
@@ -1,2 +1,2 @@
-This is the 1st experiment
-There are 3 conditions
+asjdhsajhd
+217336
diff --git a/experiment_2.txt b/experiment_2.txt
index bfa0eae..4b09371 100644
--- a/experiment_2.txt
+++ b/experiment_2.txt
@@ -1 +1,3 @@
 This is the 2nd experiment
+1273672
+aodhahd

Exercise: Compare two specific commits.

Solution
git diff HEAD~3 HEAD~2 # alternatively, use the commit hashes
diff --git a/experiment_1.txt b/experiment_1.txt
index e9d916c..b05b729 100644
--- a/experiment_1.txt
+++ b/experiment_1.txt
@@ -1 +1,2 @@
 This is the 1st experiment
+There are 3 conditions

Section 3: Ignoring Files

Background

Per default, Git tracks every file in the directory. However, some files and folders should not be tracked. For example, Git is not intended to be used for large files, so you should not let Git track your neural recordings. To prevent Git from tracking specific files, we can create a file called .gitignore and add the to-be-ignored files. .gitignore is simply a text file where you can either list individual files or patterns such as *.pdf or subject* which means “ignore every file/folder ending with .pdf or starting with subject”. There are also .gitignore generators that provide ready-made templates with files and patterns that are commonly ignored for a given language.

Importantly, adding a file to .gitignore that is already tracked by git will not automatically make Git drop it. To do this, you need to call git rm --cached <file> which will remove them from Git’s index but keep them as files in your working directory. While Git will ignore changes in these files going forward, it will not remove them from the commit history so they can still be restored later. Note that editing .gitignore and running git rm --cached are changes that need to be committed to be effective.

Exercises

In the following exercises you will create a .gitignore file, add files to ignore and observe how this affects Git’s behavior. Here are the commands you need to know:

Code Description VS Code GUI
git status Show the status of changes in the working directory Click Source Control (Ctrl + Shift + G) → See the file changes
git add f1.txt f2.txt Stage specific files for commit Click ➕ (plus) icon next to the files in Source Control
git add . Stage modifications of all files Click ➕ (plus) icon next Changes
git commit -m "Commit message" Commit staged files with a message Click ✔ (checkmark) icon, enter commit message, and press Enter
git commit -am "Commit message" Stage and commit all modified files in one step Click “Stage All Changes”, then ✔ (checkmark) icon
git rm --cached <file> Remove the file from Git’s index N/A (command line only)

Exercise: Create the .gitignore file in your folder and add experiment_4.txt to it. Then stage and commit the file.

Solution
# after creating gitignore and adding experiment_4.txt:
git add .
git commit -m "add .gitignore"
[master 898fd5e] add .gitignore
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore

Exercise: Create experiment_4.txt and check git status. You should not see any untracked changes because the file should be ignored.

Solution
git status
On branch master
nothing to commit, working tree clean

Exercise: Add *.txt to .gitignore to ignore all text files and commit.

Solution
# after adding *.txt to .gitignore
git commit -am "ignore all text files"
[master 5065227] ignore all text files
 1 file changed, 1 insertion(+), 1 deletion(-)

Example: Use git rm --cached to remove experiment_1.txt from Git’s index but keep it in your working directory and commit.

Solution
git rm --cached experiment_1.txt
git commit -am "rm experiment_1.txt"
rm 'experiment_1.txt'
[master 535d37a] rm experiment_1.txt
 1 file changed, 2 deletions(-)
 delete mode 100644 experiment_1.txt

Exercise: Use git rm --cached to remove experiment_2.txt from Git’s index but keep it in your working directory and commit.

Solution
git rm --cached experiment_2.txt
git commit -am "rm experiment_2.txt"
rm 'experiment_2.txt'
[master 162164d] rm experiment_2.txt
 1 file changed, 3 deletions(-)
 delete mode 100644 experiment_2.txt

Exercise: Modify experiment_2.txt and check git status - it should not show any changes.

Solution
git status
On branch master
nothing to commit, working tree clean

Section 4: Reverting to Older Versions

Background

Because Git keeps the full history of all files, it allows you to go back to a previous version of your work if needed. There are several ways of doing this - in this section, we will use git revert. Reverting is a safe mechanism for restoring previous file versions because you do not risk losing anything. Revert will simply create a new commit to undo a selected commit. This is where granular commits with descriptive messages are very useful because they allow you to identify and revert specific changes.

The downside of git revert is that it may cause merge conflicts. If you are trying to undo a line that has been modified again in later commits git does not know what to do. Should it undo the requested commit as well as the later one or keep the later one and ignore the revert? In these cases, Git will warn you about a merge conflict which means that you have to select manually what to keep and what to drop. While handling merge conflicts is beyond the scope of this notebook, you’ll at least see how this looks.

Exercises

In the following exercises, you’ll use revert to restore previous versions of the repository. You’ll also see how merge conflicts can happen when reverting.

Code Description VS Code GUI
git log --oneline Shows commit history in a short format NA
git revert HEAD Revert the most recent commit NA
git revert <commit-hash> Creates a new commit that undoes only the specified commit NA

Exercise: Use git log --oneline to view the commit history.

Solution
git log --oneline
162164d (HEAD -> master) rm experiment_2.txt
535d37a rm experiment_1.txt
5065227 ignore all text files
898fd5e add .gitignore
350827e add more data to exp 1 and 2
c5ff83b add data to exp 2 and modify exp 1
4c2c324 add data to exp 1
24a6eeb add files

Exercise: Revert the latest commit using git revert HEAD. This may open the text editor VIM to edit your commit message. To exit VIM, press Esc, type :wq (w for write, q for quit) and hit Enter.

Solution
git revert HEAD
[master 4896dd1] Revert "rm experiment_2.txt"
 Date: Fri Apr 24 13:46:16 2026 +0200
 1 file changed, 3 insertions(+)
 create mode 100644 experiment_2.txt

Exercise: Check the git log to view the latest commit made by revert (you can use git log -1 to show only the most recent commit).

Solution
git log -1
commit 4896dd1727fe09c7e66e759c0a71be42f32ede22 (HEAD -> master)
Author: atleer <atle.rimehaug@gmail.com>
Date:   Fri Apr 24 13:46:16 2026 +0200

    Revert "rm experiment_2.txt"
    
    This reverts commit 162164d2bede5a917442ec6f4468b5ceb5eedf3e.

Bonus Exercise: Replace the entire content of experiment_2.txt and commit. Then, identify and git revert the commit where you modified experiment_2.txt for the first time. Does this cause a merge conflict? What does the message say?

Solution
# after changing the content of experiment_2.txt:
git revert HEAD~6
[master e94782b] New version of experiment_2.txt
 1 file changed, 1 insertion(+), 3 deletions(-)
CONFLICT (modify/delete): experiment_1.txt deleted in HEAD and modified in parent of 350827e (add more data to exp 1 and 2).  Version parent of 350827e (add more data to exp 1 and 2) of experiment_1.txt left in tree.
Auto-merging experiment_2.txt
CONFLICT (content): Merge conflict in experiment_2.txt
error: could not revert 350827e... add more data to exp 1 and 2
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git revert --continue".
hint: You can instead skip this commit with "git revert --skip".
hint: To abort and get back to the state before "git revert",
hint: run "git revert --abort".