Git Gud

Floris van den Bosch

February 26, 2024

What is Git?

  • version control system (VCS)
  • used on the command line
  • Distributed version control

Version Control System (VCS)

manage changes made to collections of information


Examples

  • editions in books
  • revisions of technical standards
  • software versions

Local VCS

All version of your project are on your local PC

local VCS

Centralized VCS

All versions are on a server


Easy to manage, but has a single point of failure

centralized VCS

Distributed VCS

Everyone has a copy of the project.


This allows for more flexible setups.

distributed VCS

Why use Git?

Improve project management

  1. As lab journal for your scripts
  2. Identify when bugs are introduced
  3. Share and collaborate on projects

Git works offline

  • Make and store changes offline
  • Project history is stored offline
  • Only need a connection when sharing

Git basics

New/Existing project

git init

This creates the .git subdirectory that stores all versions

We now have a git repository!

example

We create a new game based on werewolves and quantum mechanics

$ mkdir quantumwerewolf
$ cd quantumwerewolf
$ git init

Copy a project

git clone <project-url>

The project is copied into the current directory

example

We clone a game about werewolves and quantum mechanics

$ git clone https://github.com/ProodjePindakaas/Quantum-Werewolf.git
$ cd quantumwerewolf

Making changes

We found a bug!

if answer.isdigit():  # <-- This is the problem
    i = int(answer) - 1
    if i in range(self.player_count):
        answer = self.players[i]

Let’s fix it

if answer.isdecimal():  # Now it works!
    i = int(answer) - 1
    if i in range(self.player_count):
        answer = self.players[i]

Staging the fix

We now have made a change, but git has not recorded it yet

We need to tell git which changes to record with

git add <file>

These changes are now staged

Committing the fix

Now the file is ready to be committed

This will permanently add the changes to the file history

Committing the fix

git commit

This will your text editor where you can describe the changes in this commit in detail.

Or short description with the -m option

git commit -m "Fixed the bug"

Overview of file states

Lifecycle of file statuses

Viewing changes

git status

View the current state of all files

$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
 (use "git restore --staged <file>..." to unstage)
       modified:   staged_file.txt


Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
       modified:   modified_file.txt

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

File changes

See what you modified but have not staged with

$ git diff

Or see what you have staged but not committed

$ git diff --staged
Example
diff --git a/quantumwerewolf/cli.py b/quantumwerewolf/cli.py
index 6cfa32f..1a38216 100644
--- a/quantumwerewolf/cli.py
+++ b/quantumwerewolf/cli.py
@@ -70,7 +70,7 @@ class CliGame(Game):
    def ask_player(self, query, invalid_players=[]):
        answer = input(query + 'Name: ')

-        if answer.isdigit():
+        if answer.isdecimal():
            i = int(answer) - 1
            if i in range(self.player_count):
                answer = self.players[i]

View history

git log

Get a summary of the commit history

$ git log
commit 5658af2ab70472d25bb47cf6a62468ec0b966ed3 (main)
Author: Floris van den Bosch <f.vdbosch98@gmail.com>
Date:   Wed Feb 28 11:06:31 2024 +0100

   Fixed the bug

Intermission

git init

git clone

git add

git commit

git status

git diff

git log

Using branches

Linear History

Without branching our project history looks like this

Note: the default branch may also be called main

Creating a Branch

git branch <branch-name>

this creates a new branch with the name <branch-name>

Usually branches are created for a specific purpose

  • fixing a bug
  • a new feature

Selecting a branch

We can check out the new branch

git checkout <branch-name>

Moves the HEAD to this branch and changes our local files accordingly.

example

We create new branch for the bug

$ git branch iss53
$ git checkout iss53

example

With git status we can see on which branch we are

$ git status
On branch iss53

example

Now we make the changes as before and commit them with git commit

Merging branches

When a branch is done and tested we want to merge it into the main branch

git checkout <master-branch>

git merge <branch-to-merge>

example

When all is we and tested we merge the fix for the issue.

$ git checkout master
$ git merge iss53
Updating f42c576..3a0874c
Fast-forward
quantumwerewolf/cli.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

example

This we call a fast-forward, as the history is still linear

diverging example

We are working on two branches

diverging example

first we merge the hotfix into master

This is a simple fast-forward

diverging example

The hotfix branch can now be deleted

diverging example

Finally, when the iss53 branch is done we merge this into the master branch

Conflicts

If we alter the same line in the same file, we get a conflict

example

When a conflict occurs git does not make a new commit

$ git merge iss53
Auto-merging example.txt
CONFLICT (content): Merge conflict in quantumwerewolf/cli.py
Automatic merge failed; fix conflicts and then commit the result.

example

git mergetool

This detects the conflict and lets you fix them in your favorite mergetool (e.g. vimdiff)

After the manual merging we still need to commit the merge.

Branching workflow

  • master: stable branch used for production
  • develop: semi-stable branch with finished features
  • topical: separate branch for each feature/bug

Rebasing

git rebase <rebase-to-branch>

An alternative to a merge (or fast-forward) is the rebase

It fast-forwards the branching point of the branch

Best practice: avoid using rebases

example

We have a simple divergence

example

We could do a regular merge

$ git checkout master
$ git merge experiment

example

Instead, we rebase the experiment to the master branch

$ git checkout experiment
$ git rebase master

example

Now we move the master branch forward again

$ git checkout master
$ git merge experiment

Working with Remotes

Remotes

A remote is a copy of the project’s repository in another location

Examples

  • on Github/Gitlab
  • a different machine in a network
  • a different folder on the same machine

Note: Using git clone automatically adds the remote origin

Showing Remotes

git remote

This shows a list of current remotes

git remote -v

This shows the URLs for each remote

example

$ git clone https://github.com/ProodjePindakaas/Quantum-Werewolf.git
$ cd quantumwerewolf
$ git remote
origin
$ git remote -v
origin  https://github.com/ProodjePindakaas/Quantum-Werewolf.git (fetch)
origin  https://github.com/ProodjePindakaas/Quantum-Werewolf.git (push)

Pushing and Pulling

git fetch <remote>

Gets the history on your remote

git push <remote> <branch>

Sends and add your changes to the remote

git pull <remote> <branch>

Retrieves and merges changes from the remote

example

Sending the bugfix to the remote (Github)

$ git push -u origin iss53


Note: -u is needed as the iss53 branch does not exist yet on the remote

example

Or get the fix from the remote

$ git pull origin iss53


Note: this might be one situation to use rebase if you haven’t pulled from the remote in a long while.

Adding remotes

git remote add <name> <url>

Adds a new remote

example

We want to run the game on a cluster

me@cluster $ git clone me@host:/path/to/repository
me@host    $ git remote add cluster me@cluster:/path/to/copy

Now we can push our program to the cluster!

Hosting repositories

self-hosting options:

  • bare repository with SHH/HTTPS
  • Self hosted web interface: GitWeb or Gitlab
  • Third party hosting: Github or Gitlab

Tips and Tricks

gitignore

Local files that you do not want to record:

  • temporary/cache files
  • Output files

Declare files/folders you want git to ignore in .gitignore files

Hint: you can find premade .gitignore files in github.com/github/gitignore for many programming languages

Tags

By default, git gives every version (commit) a hash to identify it

git tag <version>

Allows you to manually tag the current version (ex. git tag v3.1.4)

You can also tag a previous commit with its hash

git tag <version> <hash>

stash

We all make mistakes

git stash

Temporarily store your file modifications

git stash pop

Applies the stored changes to your current branch

git tree

The git log command shows a linear history by default

git log --oneline --graph --color --all --decorate

Shows the branches explicitly as ‘timelines’

Aliases

You can alias command and options you often use

git config alias.<alias-name> <command>

such as

git config --global alias.tree 'log --oneline --graph --color --all --decorate'

Now we can just run git tree!

rerere

You may run into the same conflict numerous times

git config --global rerere.enabled true

Enabling ReReRe (Reuse Recorded Resolution) automatically applied your conflict resolutions to future occurences

Further reading

Inspired by and images from Chacon, S., & Straub, B. Pro Git (Version 2)

https://github.com/progit/progit2