Git basics

Goals of the day

  • Understand git terminology, structure
  • Be comfortable using basic git commands
  • Become autonomous to continue your learning journey on your own

Why do we keep doing this?

  • report.odt
  • report_final.odt
  • report_v2_final.odt
  • report_final_fixed.odt
  • report_final_20220706.odt
  • Fear of losing data
  • Need to have a version that’s considered "current"

Git to the rescue

"Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. "

Git to the rescue

Initial report

Add table of contents

Add part about foobar

Fix typos

Each change is recorded, which means you basically have a time machine 🤯

Never* fear losing your work again!

* unless you screw up, which happens

Let’s install it!

Today’s most important concepts

  • Repository
  • Commit
  • Branch
  • Working directory
  • Staging area

Let’s create a repository

$ mkdir myproject
$ cd myproject
$ git init
$ ls -a
.git/

The git repository

apps/

main.py

README.md

Initial report

Add table of contents

Add part about foobar

Fix typos

.git

myproject

Git stores all its data in the .git directory

The git repository

apps/

main.py

README.md

Initial report

Add table of contents

Add part about foobar

Fix typos

.git

myproject

Git stores all its data in the .git directory

Our first commits!

# Create a file named main.py in the "myproject" directory

def say_hello():
    # Typo here is on purpose
    print("helo!")

Our first commits!

$ git status
On branch main

No commits yet

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

nothing added to commit but untracked files present
(use "git add" to track)

git status: check the status of the working directory and the staging area

Our first commits!

$ git add main.py
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
    new file:   main.py

git add: add files to the staging area

Our first commits!

$ git diff --staged
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..0c9e0e1
--- /dev/null
+++ b/main.py
@@ -0,0 +1,2 @@
+def say_hello():
+    print("helo!")

git diff: check the differences between versions of a file

Our first commits!

$ git commit -m "Hello world :)"
[main (root-commit) 6ba367f] Hello world :)
 1 file changed, 2 insertions(+)
 create mode 100644 main.py
 
$ git status
On branch main
nothing to commit, working tree clean

git commit: create a commit with the changes from the staging area

Writing a good commit message

Explain what your commit changes in the code (and why)

Writing a good commit message

  • ❌ Fix
  • ❌ Try something
  • ❌ Fix tests
  • ❌ Add some code to improve the layout
  • ❌ Added a checkout button on the basket page
  • ✅ Add a checkout button on the basket page
  • ✅ ✅ mantis612 - add a checkout button on the basket page

It’s sometimes a good idea to provide more information

mantis612 - add a checkout button on the basket page


Currently users cannot do a checkout because the fixed position button is not always visible. Adding it on the basket page was discussed and validated by the national committee.

1 line of text (~80 chars max)

1 blank line

Explanation

Recovering files

# Delete the main.py file

$ ls -a
.git/

$ git status
On branch main
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    deleted:    main.py
    
$ git restore main.py
$ ls -a
.git/
main.py

git restore: restore files in the working directory or in the staging area

Manipulating the git repository

git add

git commit

git checkout

Use git status to check the status of the working directory & staging area

git restore

git restore --staged

Exercise

Fix the typo in the main.py file and record the change in the git repository

Hint: use the add & commit commands

Exercise

# Fix the typo in the main.py file and then:
$ git add main.py
$ git commit -m "Fix typo"

Inspecting the history

$ git log
commit 087a9263a2ea4fe11c384e0ca1699f7741264a9c (HEAD -> main)
Author: Sylvain Fankhauser <sephi@fhtagn.top>
Date:   Tue Jun 7 11:49:27 2022 +0200

    Fix typo

commit 6ba367f9dd9e564ab18735a3306eec23b2aaa6d1
Author: Sylvain Fankhauser <sephi@fhtagn.top>
Date:   Tue Jun 7 11:34:06 2022 +0200

    Initial commit

git log: show the commit history

Hint: use `git log -p` to see the changes!

Hint2: use `git log --graph` to see a nice graph!

What is a commit?

commit 087a9263a2ea4fe11c384e0ca1699f7741264a9c
Author: Sylvain Fankhauser <sephi@fhtagn.top>
Date:   Tue Jun 7 11:49:27 2022 +0200

    Fix typo

 main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)

A unique identifier (generated)

An author

A date

A message

A set of changes

+ one or several parents

parent 6ba367f9dd9e564ab18735a3306eec23b2aaa6d1

Viewing a specific commit

$ git show 087a9263a2ea4fe11c384e0ca1699f7741264a9c
commit 087a9263a2ea4fe11c384e0ca1699f7741264a9c (HEAD -> main)
Author: Sylvain Fankhauser <sephi@fhtagn.top>
Date:   Tue Jun 7 11:49:27 2022 +0200

    Fix typo

diff --git a/main.py b/main.py
index 0c9e0e1..d55f52f 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,2 @@
 def say_hello():
-    print("helo!")
+    print("hello!")

git show: show the details of a given object

Annotate a file with its changes

(aka "finding out who to blame for the bug")

$ git blame main.py
^6ba367f (Sylvain Fankhauser 2022-... 1) def say_hello():
087a9263 (Sylvain Fankhauser 2022-... 2)     print("hello!")

git blame: show the commit associated with each line of a file

Reverting a change

$ git log -p
commit a0ac2b55d97ec3ca7e70a58870241a1bf0d1328e
Author: Bernard Pivot <bernardi@pivot.fr>
Date:   Tue Jun 7 13:54:30 2022 +0200

    FRANCISONS TOUT!

diff --git a/main.py b/main.py
index d55f52f..18bfdaa 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,2 @@
-def say_hello():
+def dire_bonjour():
     print("hello!")
     
$ git revert a0ac2b55d97ec3ca7e70a58870241a1bf0d1328e
[main 5f6e6c8] Revert "FRANCISONS TOUT!"
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git log -p
commit 5f6e6c89fc0e7db8a2958029c8932a46e00f01c7 (HEAD -> main)
Author: Sylvain Fankhauser <sephi@fhtagn.top>

    Revert "FRANCISONS TOUT!"

    This reverts commit a0ac2b55d97ec3ca7e70a58870241a1bf0d1328e.

diff --git a/main.py b/main.py
index 18bfdaa..d55f52f 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,2 @@
-def say_bonjour():
+def say_hello():
     print("hello!")

commit a0ac2b55d97ec3ca7e70a58870241a1bf0d1328e
Author: Bernard Pivot <bernard@pivot.fr>
Date:   Tue Jun 7 13:54:30 2022 +0200

    FRANCISONS TOUT!

diff --git a/main.py b/main.py
index d55f52f..18bfdaa 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,2 @@
-def say_hello():
+def say_bonjour():
     print("hello!")

git revert: create a commit that reverts the changes of another commit

Working on multiple features in parallel

mantis769: update changelog

mantis722: fix tab label

mantis769: recreate LDAP users

mantis722: show cancelled tickets

Problem: we can’t easily release only mantis769!

Update changelog for release 2.13

mantis769: update changelog

mantis722: fix tab label

mantis769: recreate LDAP users

mantis722: show cancelled tickets

Solution: work on different branches!

Update changelog for release 2.13

Merge branch mantis769

Working on multiple features in parallel

mantis722

mantis769

main

main

A branch is a pointer to a commit

mantis769: two

mantis722: two

mantis769: one

mantis722: one

Solution: work on different branches!

Update changelog for release 2.13

Working on multiple features in parallel

$ git checkout -b mantis769
$ git commit -m "mantis769: one"
$ git commit -m "mantis769: two"
$ git checkout main
$ git checkout -b mantis722
$ git commit -m "mantis722: one"
$ git commit -m "mantis722: two"

main

mantis769

mantis769

mantis769

mantis722

mantis722

mantis722

git checkout: switch branch

The branch pointer moves automatically when you make a new commit!

A branch is nothing more than a label on a commit, really

$ ls .git/refs/heads
main  mantis722  mantis769

$ cat .git/refs/heads/mantis722
5f6e6c89fc0e7db8a2958029c8932a46e00f01c7

$ git show-ref mantis722
5f6e6c89fc0e7db8a2958029c8932a46e00f01c7 refs/heads/mantis722

Tags are also labels on commits, but they don’t move once they’re created

git tag: list tags or create a tag on the current commit

Create a branch named "french" where you’ll add a function "dire_bonjour" and a branch "german" where you’ll add a function "hallo_sagen".

 

Both branches should be based on the main branch.

Exercise

add dire_bonjour

add hallo_sagen

Fix typo

main

french

german

Sharing your work

Everything we did so far was local, which means only you could see your changes

Sharing your work by e-mail
(you probably won’t use this)

$ git format-patch --stdout HEAD^
From 5f6e6c89fc0e7db8a2958029c8932a46e00f01c7 Mon Sep 17 00:00:00 2001
From: Sylvain Fankhauser <sephi@fhtagn.top>
Date: Tue, 7 Jun 2022 13:54:36 +0200
Subject: [PATCH] Revert "FRENCHIZE ALL THE THINGS!"

This reverts commit a0ac2b55d97ec3ca7e70a58870241a1bf0d1328e.
---
 main.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main.py b/main.py
index 18bfdaa..d55f52f 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,2 @@
-def say_bonjour():
+def say_hello():
     print("hello!")
--
2.36.0

Sharing your work using a central server

(eg. GitHub, Bitbucket, Gitlab)

Push

Pull

Push

Pull

Central server

Git repository

🧑‍💻

Matthieu

Git repository

👩‍💻

Mélanie

Git repository

It’s possible to have several remotes!

Interacting with remotes

  • git clone: initialize a local repository from a remote
  • git push: push the current branch to a remote
  • git pull: fetch objects from a remote and update the current branch
  • git fetch: fetch objects from a remote
  • git remote: add/remove/edit remotes

Exercising remotes

Clone this repository
git@github.com:sephii/git-crash-course

Fix the typo in the README, commit and push the fix

What happens if you’re not the first pushing the fix?

Git internals

Let’s see what’s in the .git directory!

git show-ref: show the commit pointed by the given reference

git ls-tree: show the trees and blobs of the given reference

git cat-file: show the contents of a blob in the tree

git show: show almost anything

Git pull: case 1, new remote objects

A

B

C

Origin

git clone

D

A

B

C

Local copy

origin/main

origin/main

main

git pull

D

main

This is called a "fast-forward merge"

(no merge commit is created, the branch pointer is just moved to a new commit)

Git pull: case 2, new remote objects
& new local objects

A

B

C

Origin

git clone

D'

D

A

B

C

Local copy

D'

E

origin/main

main

origin/main

main

main

A merge commit is a commit that has more than 1 parent!

git pull: fetch objects from the remote and move the branch to the new tip (merging if necessary)

git pull

Fixing conflicts

D

D'

E

main

If D and D' made changes to the same lines of the same files, we’ll need to fix the conflict manually

A

B

C

Fixing conflicts: exercise

Clone this repository
git@github.com:sephii/git-crash-course

Switch to the `fix-typo` branch
git checkout fix-typo

Try to merge the `politeness` branch in it
git merge politeness

What happens? How do you fix this?

Rewriting the last commit

A

B

C

D

origin/main

git commit --amend: remove last commit
and create a new one

Oops

origin/main

$ git commit -m "D"
$ git commit --amend -m "D2"

D2

DON’T DO THIS IF THE COMMIT

HAS BEEN PUSHED

DON’T DO THIS IF THE COMMIT

HAS BEEN PUSHED

DON’T DO THIS IF THE COMMIT
HAS BEEN PUSHED

D O N ’ T

Rewriting history

DANGER ZONE AHEAD

DANGER ZONE AHEAD

DANGER ZONE AHEAD

DANGER ZONE AHEAD

You have been warned.

Rewriting history

A

B

C

D

git rebase: rewrite history (and potentially
screw up your whole repository)

Oops

origin/main

$ git rebase -i A

pick 3e2a4d2 B
pick cce25f3 C
pick ccf7b1b D

edit 3e2a4d2 B
pick cce25f3 C
pick ccf7b1b D

$ # Make some changes…
$ git commit --amend
$ git rebase --continue

B'

C

D

origin/main

origin/main

origin/main

I will not rewrite the history once it’s published.

I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.

I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.
I will not rewrite the history once it’s published.

Low-level history rewriting

git reset: move your branch pointer to a given commit

git cherry-pick: replay a commit

A

B

C

D

origin/main

$ git reset --hard B
$ git cherry-pick D

C

D

origin/main

origin/main

D

origin/main

The part where I wish I had slides

Staging parts of files

git add -p: stage only parts of files

git restore -p: remove parts of files from the staging area

-p stands for "patch"

Stashing your work

git stash: stash the changes in your working directory

git stash list: list the contents of the stash

git stash pop: pop changes from the stash and apply them

(these commands work with "-p")

What to do when pushing fails

Recovering from fuck ups

git reflog: show the log of branch tip switching

Viewing differences

Useful revision patterns

  • - : "the previously checked commit"
  • @{u}: the commit pointed by the remote branch
  • A..B : "commits in B that are not in A"
  • A...B: difference between the two sets of commits (ie. "commits in A not in B + commits in B not in A")

If we still have time

  • - : "the previously checked commit"
  • @{u}: the commit pointed by the remote branch
  • A..B : "commits in B that are not in A"
  • A...B: difference between the two sets of commits (ie. "commits in A not in B + commits in B not in A")

If again we still have time

  • Tracking bugs with git bisect
  • Using aliases
  • Finding commits with grep
  • Multiple worktrees

Going further

`git help`

Thank you!

Git basics

By Sylvain Roflmao

Git basics

  • 230