git & gitlab

Gerhard Bräunlich

January 2022

git

Distributed file versioning

  • Version locally
  • Sync with remote
  • Support for collaborative work on the same project
🐧 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 (use --local instead of --global)

Clone an already published project (repository)

git clone <url>

Initialize a new repository:

git init

Commits

Idea: Organize changes to your project in “commits” (a new state in the history of changes)

A commit is a “logical” unit of change.

  • As small as possible (as large as necessary)
  • Should not break code / test / builds (sometimes this cannot be avoided)
  • rule of 👍: commit message is short and does not contain any “and”-connected statements

👎 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 example:

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

👎 Bad example:

Git Commit

Source: xkcd.com/1296

Local Workflow

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

    git add file1 file2 ...
  2. Commit changes:

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

  4. Back to step 1

To unstage files (but keep local changes) again, use

git reset file1 file2 ...

To discard changes to files, use

git restore file1 file2 ...

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

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

Things that should NOT be versioned

  • Intermediate files (.o / .obj files)
  • Final binaries (use gitlab artifacts / releases / dedicated download page)
  • Platform specific files (e.g. Thumbs.db, .DS_Store) or directories (e.g. __MACOSX)
  • User specific directories (e.g. .vscode/)

Use .gitignore to declare this sort of files:

*.o
*.obj

Also version .gitignore!

When things should NOT go into .gitignore

For example private folders filled with your own 🗑

Use the “private” version of .gitignore, which wont be published:

.git/info/exclude

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
git diff
# Add a commit ref as an argument, to see changes relative
# to the corresponding commit
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

Working on branches

When:

  • Extending project for a new feature
  • Trying-out alternative solution
  • Tracing and fixing bugs
  • Doing essentially any kind of independent development chunk …

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

Publishing on gitlab.ethz.ch

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

  • Create a new project

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

    git remote add origin git://gitlab.ethz.ch/your-username/your-project-name
    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 wokflow

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

  • Create a new issue for your task (fix a bug, add a feature) if not already created by a project leader

  • From within the issue, create a new “merge request”. This will create a 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 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

  • Clean history: Update source branch for changes in target branch

    Best update by means of rebase / git commit --amend

    git switch target-branch
    git pull
    git switch source-branch
    git rebase target-branch  # might need to resolve conflicts!
    git push --force-with-lease  # with lease in case others pushed meanwhile

    others (e.g. reviewer) can update:

    git switch source-branch
    git pull --rebase
  • Don’t change commits on the common branch. Only “self owned” feature branches are allowed to be altered.
  • Git commit message convention (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

Exercise

Visit

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

and follow the instructions there.

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

When trying to merge / rebase, git will detect the conflict and mark the affected lines in the affected files.

# Authors

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

Professor X et al.

Advanced

What to do when you messed up commit?

Add changes to the most recent commit (rewrite commit):

git add file1 file2 ...
git commit --amend --no-edit # Without `--no-edit`, you also
#  can alter the commit message.
git push --force-with-lease # force push
# only use -f instead of --force-with-lease if you really
# know what you're doing

Only do this to your “self owned” unmerged feature branches, never on the common 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.

Edit or drop older commits / insert commits:

git rebase -i HEAD~4 # Change 4 to the number of commits between
# the current commit and the commit you want to change
# (including the current commit and the commit you
# want to change)

or

git rebase -i <commit-id-before-start-of-rebase>

This also allows for squashing two commits into one and deleting commits.

Hard reset to a commit or branch (checkout and delete all following commits):

git reset --hard <commit-ref>
# <commit-ref> also can be a name of a branch

Rebasing on an other branch:

git switch feature
git rebase main

Merging branches locally:

git switch main
git merge feature

Acknowledgements

Slides:

Mik Rybinski for a careful review