git & gitlab

https://siscourses.ethz.ch/git-workshop

Gerhard Bräunlich

git

Distributed file versioning

  • Local versioning system
  • Sync with remote repository
  • Support for collaborative work

Why?

paper



└── paper.tex




Why?

paper



├── paper.tex
├── paper-v02.tex
├── ...
└── paper-v(n).tex

Why?

paper
├── paper-2018-03-02.tex
├── paper-2018-03-15.tex
├── paper-2018-03-16.tex
├── paper.tex
├── paper-v02.tex
├── ...
└── paper-v(n).tex

Why?

paper
├── paper-2018-03-02.tex
├── paper-2018-03-15.tex
├── paper-2018-03-16.tex
├── paper.tex
├── paper-v02.tex
├── ...
├── paper-v(n).tex
└── paper-v(n+1).tex

Why?

🤯 Filesystem Chaos

paper
├── ...
├── paper-2018-03-(n).aux
├── paper-2018-03-(n).log
├── paper-2018-03-(n).pdf
├── paper-2018-03-(n).tex
└── ...

Why?

🤔 Branching: Missing history

Why?

🤢 Redundancy of information

...
content
...
...
content
added content
...

Setup

🐧 apt-get install git / yum install git
🪟 gitforwindows.org
🍏 git-scm.com/download/mac

First time setup

git config --global user.name "Chuck Norris"
git config --global user.email "chucknorris@roundhouse.gov"

Also can be done per repository (omit --global)

git config --global core.editor "code --wait"

Initialize a new repository:

git init

Clone an already published project (repository)

git clone git@gitlab.ethz.ch:sis/courses/git-workshop/git.git
git clone https://gitlab.ethz.ch/sis/courses/git-workshop/git.git

Commits

Idea: Organize changes to your project in “commits”.

commit: “consistent” unit of change.

  • As small as possible
  • As large as necessary
  • Should not break code / test / builds
  • Sometimes this cannot be avoided

Commit messages

  • rule of 👍: commit message is short and does not contain any “and”-connected statements
  • Conventions (sources: 1, 2, 3, 4, 5, 6, 7):
    • Separate subject from body with a blank line
    • Limit the subject line to 50 characters
    • Capitalize the subject line
    • Do not end the subject line with a period
    • Use the imperative mood in the subject line
    • Wrap the body at 72 characters
    • Use the body to explain what and why vs. how

👎 Bad examples:

  • Changes of 2021-12-01
  • Changes of 2021-12-02
  • Changes of 2021-12-02 (part 2)

  • New function / Bugfix
  • Other function / Remove old code
  • Fix new function / Clean comments

👍 Good examples:

  • Fix bug in routine A
  • Add function print_42()
  • Rename argument of print_42()
  • Reformat codebase

👎 Bad example:

xkcd.com/1296: Git Commit

👎 Bad examples:

  • [This patch] fixes a segfault in routine A

  • [I] fixed a segfault in routine A

👍 Good example:

  • Fix a segfault in routine A

Local Workflow

  1. Stage files (mark files to be included in the next commit):

    git add file1 file2 ... # if not yet tracked, tracking starts here
  2. Commit changes:

    git commit -m "Description of the changes"
  3. Continue work

  4. Back to step 1

To view what is staged / changed, use

git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged ..." to unstage)
    modified:   paper.tex

Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git restore ..." to discard changes in working directory)
    modified:   img/figure.svg

Untracked files:
  (use "git add ..." to include in what will be committed)
    img/new-figure.svg

To stage all tracked, changed files, use

git add -u # safer than git add .

Tipp: you also can stage only parts of changed files:

git add -p file1 file2 ...
# -e for to even edit the diff

To untrack but not remove the file, use

git rm --cached <file>

Viewing changes

git status
git log
git log <commit-ref>
# e.g. <commit-ref>=11089693 (commit-ref is longer but
# max. first 8 chars suffice per repo)
git log filename
git log -p filename

Pro-tip: Customize git log:

git config --global alias.lg "log --color \
  --graph \
  --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s ' \
                  '%Cgreen(%cr) %C(bold blue)<%an>%Creset' \
  --abbrev-commit"

(omit command line options to see, if you actually like the effect)

This enables:

git lg
git diff               # Changes relative to the last commit
git diff <commit-ref>  # Changes relative to <commit-ref>
git diff <branch-name> # Changes relative to <branch-name>
git diff --staged # or --cached
git blame filename
# Add -L 40,60 to only see changes between line 40 and 60
# Add -L '/^double f(double x)/' to only see changes on lines
#  matching the regexp
# Add --since=3.weeks to only show changes 3 weeks back

Exercises

Things that should NOT be versioned

  • Intermediate files / by-products
  • Final binaries
  • Platform specific files / directories
  • User specific directories
  • ⚠️ Plain text passwords

Use .gitignore to declare this sort of files:

# Intermediate files / by-products
*.o
*.obj
*.log
# Final binaries
*.pdf
*.exe
# Platform specific files / directories
Thumbs.db
.DS_Store
__MACOSX/
# User specific directories
.vscode/

Also version .gitignore!

Templates for .gitignore available at

https://github.com/github/gitignore

Large data files

E.g. large images for papers

Use git-lfs: git-lfs.com (Large File Storage)

Exercise: git-katas/ignore

When things should NOT go into .gitignore

project
├── README.md    # <- versioned
├── src
│   └── main.cpp # <- versioned
├── build        # <- binaries => should go to .gitignore
│   └── ...
└── private      # Uncommon / not used by most other devs
    ├── my_private_garbage # 🗑
    └── ...

Use the “private” / untracked version of .gitignore:

.git/info/exclude

Git for collaboration

When:

  • work on individual features
  • try out alternative solutions
  • trace and fix bugs

Then: git branches

Create a new branch out of the current git state:

git switch -c branch-name

Once you continued work and want to go back to the state of a branch:

git switch branch-name

Go back to previous branch:

git switch -

Delete branch:

git branch -d branch-name

Rename branch:

git branch -m old-branch-name new-branch-name

Exercise

Publishing on gitlab.ethz.ch

  • Log in to https://gitlab.ethz.ch

  • New project

  • Follow the instructions there under “Push an existing Git repository”

    git remote add origin git://gitlab.ethz.ch/username/project
    git push -u origin --all
  • To also publish different branches:

    git switch other-branch
    git push -...

Exercise

  • Create a new local git repository.

  • Make an initial commit with commit message "1st commit", containing one single file: README.md. Content:

    # Top Secret Project
  • Create a 2nd branch named “secret”.

  • Change the file README.md, so it now contains:

    # Top Secret Project
    
    We don't yet know, what it is good for.
  • Commit the changes (commit message: "Add description to README.md")

  • Publish both branches (main, secret) to a private project on gitlab.ethz.ch (user space).

    Dont forget to uncheck the box “create README.md”!

  • Give me access to the repos, you created (@brgerhar).

Distributed synchronisation workflow

How to share changes with others?

Rebase vs merge: rebase

git switch feature
git rebase main

Rebase vs merge: merge

git switch main
git merge feature

Exercise

learngitbranching.js.org:

    1. Merging in Git
    1. Rebase introduction
  • Rebasing over 9000 times

Resolving conflicts

A commit in a feature branch changes the same line as a change commited to the main branch in the meantime.

Example

commit d97360f (HEAD -> main)
Author: Student B <b@ethz.ch>
Date:   Sat Dec 11 17:13 2021

    Add authors

diff --git a/AUTHORS.md b/AUTHORS.md
index 0ca9600..61e8a1b 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -1 +1,4 @@
 # Authors
+
+* Student B
+* Student A
commit 6b3c423 (HEAD -> feature-a)
Author: Student A <a@ethz.ch>
Date:   Sat Dec 11 17:13 2021

    Add authors

diff --git a/AUTHORS.md b/AUTHORS.md
index 0ca9600..a15a7dd 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -1 +1,4 @@
 # Authors
+
+* Student A
+* Student B

During merge / rebase, git detects conflicts and marks affected lines in affected files:

# Authors

<<<<<<< HEAD
* Student B
* Student A
=======
* Student A
* Student B
>>>>>>> 6b3c423 (Add authors)

After resolving the conflict, the file could look like this:

# Authors

Professor X et al.

What is “origin/”?

git also tracks a state of the state of the remote.

Branching workflow

Idea: Only work on “feature” branches only, let a lead developer merge the changes into a common branch.

See also this discussion.

GitLab issue-oriented workflow

  • New issue (Task: fix a bug, add a feature)

  • Create merge request (from within the issue) -> new feature branch on the remote repository

  • Fetch the automatically created branch

    git fetch   # or git pull
    # you will get a list of all
    # newly created remote branches
    git switch <branch-name>
  • Work on the feature branch locally (containing possibly multiple commits to a new feature).
  • Push changes: git push
  • Mark as readyMark the Merge request as “ready” and ask the lead developer / colleague (reviewer) for a review
  • Fix all issues found by the reviewer
  • After approval: merge the merge request

Live demo

https://gitlab.ethz.ch/sis/courses/git-workshop/demo-project

Conventions

  • Don’t change commits on shared branches (main)
  • Clean up the history on “self-owned” branches before submitting for review
  • Stick to git commit message conventions

Clean History

git commit --amend --no-edit # Without `--no-edit`, you also
# can alter the commit message.
git commit --fixup=<commit-ref>
git rebase -i <ref> --autosquash
# <ref>: branch / commit ref before commit to be rewritten
git rebase -i <ref>
# <ref>: branch / commit ref before commits to be rewritten

Rewriting the history needs a “force” push:

git push --force

others (e.g. reviewer) can update using a “force pull”:

git switch source-branch # if not already on the branch
git fetch
git reset --hard origin/source-branch

When you rewrite a commit, a force push is crucial. A normal push will be rejected and you will be instructed to do a pull first (see message below). This is not what we want to accomplish in this situation. Ignore it and do the force push.

 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '...'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Exercise

Visit

gitlab.ethz.ch/sis/courses/git-workshop/exercises/merge_requests

and follow the instructions there.

Acknowledgements

Slides:

Mik Rybinski for a careful review

The End