From dfce75c068eed00d75f5e7da08a721c4b73db751 Mon Sep 17 00:00:00 2001 From: scbj Date: Wed, 24 Jun 2026 11:50:41 +0200 Subject: [PATCH] added git-introduction written for CT at InES --- git-introduction/Makefile | 39 + git-introduction/readme.md | 752 ++++++++++++++++++ git-introduction/resources/example-commit.png | Bin 0 -> 58938 bytes 3 files changed, 791 insertions(+) create mode 100644 git-introduction/Makefile create mode 100644 git-introduction/readme.md create mode 100644 git-introduction/resources/example-commit.png diff --git a/git-introduction/Makefile b/git-introduction/Makefile new file mode 100644 index 0000000..07892a7 --- /dev/null +++ b/git-introduction/Makefile @@ -0,0 +1,39 @@ +SHELL := /usr/bin/env bash + +MD_FILES += readme.md + +PDF_FILES := $(MD_FILES:.md=.pdf) + +VERBOSITY := + + +.PHONY: all clean clean_all test pdf repdf remove_pdf debug_pdf gfm + + +clean_all:: remove_pdf + + +pdf: $(PDF_FILES) + + +repdf: remove_pdf pdf + + +remove_pdf: + @rm -f $(PDF_FILES) || true + + +debug_pdf: VERBOSITY := --verbose +debug_pdf: $(PDF_FILES) + + +%.pdf: %.md + pandoc \ + --pdf-engine pdflatex \ + --listings \ + --template ../template.tex \ + -f markdown \ + -t pdf \ + $(VERBOSITY) \ + -o $@ \ + $< diff --git a/git-introduction/readme.md b/git-introduction/readme.md new file mode 100644 index 0000000..fd7d219 --- /dev/null +++ b/git-introduction/readme.md @@ -0,0 +1,752 @@ +# Git Gud (at Git) + +This document shall serve as both kickstart and quick reference for +beginners. +Though even experienced users may learn something new. + +> For a more complete instructions have a look at [**Pro Git**][progit]. +> You can access the book either as HTML document, PDF or EPUB. +> Change the websites language for localised versions of the book. + + +## Table of contents + + +- [Git Gud (at Git)](#git-gud-at-git) + - [Table of contents](#table-of-contents) + - [Terminology](#terminology) + - [Git & Github](#git-github) + - [Git help](#git-help) + - [Configuration](#configuration) + - [Creating a repository](#creating-a-repository) + - [The concept of commits](#the-concept-of-commits) + - [Creating a commit](#creating-a-commit) + - [.gitignore files](#gitignore-files) + - [Branches](#branches) + - [Creating/switching a branch](#creatingswitching-a-branch) + - [Merging/rebasing a branch](#mergingrebasing-a-branch) + - [*Merge* or *rebase*?](#merge-or-rebase) + - [Merging](#merging) + - [Rebasing](#rebasing) + - [Resolving conflicts](#resolving-conflicts) + - [History inspection](#history-inspection) + - [History rewriting](#history-rewriting) + - [Troubleshooting](#troubleshooting) + - [I lost a commit!](#i-lost-a-commit) + - [I tried to pull, but got a merge conflict?!](#i-tried-to-pull-but-got-a-merge-conflict) + - [Further reading](#further-reading) + - [Glossary](#glossary) + + + +## Terminology + +Command examples may have arguments enclosed with angled brackets +(`<>`). +That means the user needs to substitute the argument with their +value. +The text inside the angled brackets describes the expected value. + +Additionally an argument may be enclosed by brackets (`[]`). +That means the argument is optional. + + +## Git & Github + +Some might be confused about Git and Github +They have similar names, with Github just adding a 'hub'. + +* **Git** is a version control system, that lets you save, analyse and +edit the history of files. +It can be used locally, or in combination with a server. +The latter allows for collaboration with a team and can act as backup. +Git is licensed under GPLv2. +* **Github** is a frontend for Git, which adds functions like a +webserver, access control, pull requests or issues to name a few. +It is not a usable service on its own. +Github is proprietary and owned by Microsoft since 2018. + + +## Git help + +When unsure about a Git (sub)command append `--help`. +This opens the most specific manual available for your command. + +Example: + +```sh +# show help for the 'config' subcommand +git config --help + +# show help for the 'remote add' subcommand +git remote add --help +``` + + +## Configuration + +Before starting to use Git it is recommended to configure the user +name and email at the very least. +Otherwise, Git will ask you at the first action which requires an +identity, most probably `push`. + +Git looks for a file `~/.gitconfig` (`~` = users home directory). +You can either (create and) edit the file directly, or use +`git config`. +See the example below for how to configure the user name and mail. + +``` +[user] + name = student + email = student@students.zhaw.ch +``` + +Alternatively use the following commands to let Git write the +configuration file. + +```sh +git config set user.name +git config set user.email +``` + +> For Linux users keeping their configuration files in a repository, +> distributing them with symbolic links, the Git configuration can +> include files. +> So the user and email can be stored in a separate file. +> +> ``` +> [include] +> path = /path/to/include/file +> ``` + +Sensible options to set are the editor used for commit messages, +the default branch name and whether to merge or rebase. +The value of editor is the launch command of the desired editor. +Below is an excerpt from my configuration as an example. + +```git +[core] + editor = nvim + +[init] + defaultBranch = main + +[pull] + rebase = true +``` + + +## Creating a repository + +When working with a Git frontend like Github there are two ways to +start a repository: + +* **Local:** Use `git init` to initialise the current directory as +a Git repository. This will create a directory `.git` which contains +the local information, e.g. history, commit hashes, hooks, etc. +If you want to synchronise a local repository with a server, follow +the steps for creating a remote repository and use `git remote add` +to add a remote and its URL (default remote name is `origin`). +* **Remote:** Open the frontend of choice and create a new repository. +On Github there is usually a green button in the top right corner. +Choose a name, description and licence. +Now get the URL to clone the repository locally, or set it as remote +in an existing one. +On Github click the green `Code` button and choose either **https** +or **ssh** (recommended, but requires additional setup). + +See examples for the Git commands mentioned in this section below. + +```sh +# initialise directory as git repository +# use '.' as path to initialise current directory +git init [] + +# add remote to local repository under name 'origin' +git remote add origin + +# change URL, for example to switch to ssh after cloning +# a repository over https +git remote set-url + +# clone a remote repository +git clone +``` + +> Mind, that a remote is not strictly necessary, because the local +> repository contains the full information for version control. +> A remote is useful as a backup of your information and to work with +> other contributors. + +> Choosing a licence is important, if only to deny liability for +> your code. + +> I recommend to setup a pair of ssh keys and use ssh connections +> to interact with the Git server. +> It is more secure and usually more convenient too. + + +## The concept of commits + +Before looking at how to add or change commits, it is prudent to get +an understanding of what a commit is. + +A Git history is a linked list of changes, called commits. +Each commit has one (or in case of merge commits more) parents and an +arbitrary number of children. +A commit is a delta relative to its parent(s) and does **not** +directly contain the current state. + + +## Creating a commit + +Use `git add ` to stage changes made to a file +or directory (a directory means all contained files, recursively). +List the changed files in your working tree with `git status` and show +the differences with `git diff []`. + +With `git commit` you can commit the staged changes. +This will open the configured editor. +Enter your commit message at the very top, save and close the file. + +``` + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# On branch main +# Your branch is up to date with 'origin/main'. +# +# Changes to be committed: +# modified: git-introduction/readme.md +# +``` + +You now created a commit, but it is only local, use `git push` to +upload it to the remote repository +(This may fail due to conflicts, see +[Resolving conflicts][conflicts]). + +Instead of opening an editor you can also commit with +`git commit -m ` but I prefer opening an editor, +because it gives me another chance to spot missing changes, or changes +I did not want to add to the commit. + +> It is sensible to make small commits with coherent scope. +> +> When adding a source file it is reasonable to commit the change to +> the Makefile too. +> +> However committing two code changes for distinct functionality at +> once is frown upon. +> +> The reason is, that when searching the history for a commit, it gets +> really difficult to find the change you are looking for, when +> commits contain a lot of changes. + + +## .gitignore files + +Committing files generated by the project is a **crime**, punishable +with a sentence to read the entire Git documentation. + +Manually ignoring the generated files would be quite tedious, thus +Git has a mechanism to automatically ignore changes matching a user +defined pattern. +It is very simple, just add a file `.gitignore` to any directory in +your repository. +Such an ignore file is applied for the directory they reside in and +all lower levels. +An ignore file has no effect on its parent or sibling directories. + +Here is an example of a `.gitignore`: + +```git +# ignore build directory (and all its contents) +build/ + +# ignore all pdf +*.pdf +# do not ignore specific pdf +!readme.pdf +``` + + +## Branches + +A branch is essentially a named reference to a list of commits, +originating from a commit on the main branch. +Here is an ASCII graphic to try and explain the concept. +Keep in mind, that commits (represented by capital letters) contain +only the differences to their parents. + +``` +A -- B -- D ------ G (main) + \ + C -- E -- F (branch) +``` + +Branches are a good tool to work on a project with several +contributors without solving conflicts on every commit. +Or to quickly try an idea without the risk to poison the development +branch. + +Imagine the a scenario, where you work with a colleague on a project. +Your colleague works one part and you on another but both of you have +to make changes to the glue logic inbetween. +The one who commits second will have to solve conflicts. +This still applies when both of you work on separate branches, but +only once, when you rebase or merge your branch back to mainline. +While working on your branch you never need to solve conflicts. + +Another use for branches is to develop several features concurrently. +Working with branches you jump between the features without having to +worry about one to interfere with another. +Also you can have a branch reviewed while working on a different +feature. + + +### Creating/switching a branch + +There are at least two methods to create a branch. +Here are the two that come to mind first: + +``` +# create a branch, +# but do not switch to the created branch +git branch + +# create a branch and switch to it +git checkout -b +``` + +If your working tree is clean, you can switch between branches with +the following commands: + +``` +# change to branch (or commit) +# (does not create branches or commits) +git checkout + +# change to branch +git switch +``` + + +## Merging/rebasing a branch + +When a feature is implemented and tested, it is usually to be +integrated into the main branch. + +Git provides two ways to achieve this, merging and rebasing. + +The following subchapter explains the two philosophies. + + +### *Merge* or *rebase*? + +> My quick answer is: Does not matter, but use only one strategy per +> repository. + +This chapter will give a quick explanation of what *merging* and +*rebasing* means and at the end I explain my opinion. + + +**Merging** means creating a commit with two parents. +When merging, all conflicts are resolved in one merge commit. + +Here is some ASCII art to visualise this concept: + +``` +# merge + +A -- C (main) + \ + B -- D -- E (feature branch) + +| | | | +v v v v + +A -- C ------ F (main) + \ / + B -- D -- E (feature branch) +``` + + +**Rebasing** means to recalculate the deltas from a new origin. +This way one can just add (fast forward) the commits of the feature +branch on top of the main branch. +When rebasing, conflicts are resolved in the commit of the feature +branch, that introduces the conflict. + +Here is some ASCII art to visualise this concept: + +``` +# rebase + +A -- C (main) + \ + B -- D -- E (feature branch) + +| | | | +v v v v + +A -- C (main) + \ + B' -- D' -- E' (feature branch) + +# rebased feature-branch on top of main + +| | | | +v v v v + +A -- C -- B' -- D' -- E' (main) + +# rebased main on top of rebased feature-branch +# (no conflicts -> fast forward) +``` + + +**Conclusion** both strategies have advantages and disadvantages: + +* *Merging* has the advantage of keeping more information. +* *Rebasing* has the advantage of better readability of the history. +* Some actions for history cleanup require the interactive *rebase* + tool, which may be a *rebasing* argument for some. + +I personally have a preference for *rebasing* due to the cleaner +history, but find it perfectly reasonable to use *merging*. + + +### Merging + +When merging, checkout the destination branch and merge the feature +branch into it. +You will get one merge commit, that solves all conflicts. +Both branches stay in your history and are displayed side to side in +the graph view. + +Here I explain the procedure of a merge, below you find the commands +used. + +1) First synchronise the local with the remote repository. +1) Checkout the **destination** branch. +1) Merge the feature branch into your current (destination) branch. +1) This may cause conflicts. + 1) Get a list of files containing conflicts with `git status`. + 1) Resolve conflicts (see [Resolving conflicts][conflicts]) + 1) Continue with `git merge --contine`, choose a message for the + merge commit, save and close. +1) Synchronise the remote with `git push`. + + +### Rebasing + +When rebasing, checkout the feature branch and rebase it on the main +branch. +This way all conflicts are resolved on the feature branch and the +commits can be fast forwarded to the main branch. +Rebasing requires a force push. + +Many projects prohibit force pushes on the main branch, so rebasing +the main branch on top of the feature branch is not even an option. + +> Be very careful with force pushes. +> +> Always substitue `--force` with `--force-with-lease`. +> This at least disables one to overwrite commits on the remote, +> that are not known locally. + +Here I explain the procedure of a rebase, below you find the commands +used. + +1) First synchronise the local with the remote repository. +1) Checkout the **feature** branch. +1) Rebase the feature branch to the destination branch +1) This might cause conflicts. + 1) Get a list of files containing conflicts with `git status`. + 1) Resolve conflicts (see [Resolving conflicts][conflicts]). + 1) Continue the rebase with `git rebase --continue`. + 1) Repeat until all conflicts are solved. + When rebasing, conflicts are solved in the commit, which introduces + them. + Thus one may have to repeat these steps as many times, as there are + commits to rebase. +1) Force push your rebased branch (with `--force-with-lease`). +1) Check out the destination branch. +1) Rebase destination branch to feature-branch. +1) Push rebased destination branch (this does not require force). + +```sh +# fetch the newest state from the remote +git fetch --all + +# switch branch +git checkout + +# rebase current branch onto +git rebase + +# show files containing conflicts during a rebase +git status + +# stage solved conflicts +git add + +# continue rebase after conflicts are solved +git rebase --continue + +# push rebased commits to feature branch +# +# NEVER USE `--force`, SUBSTITUE WITH `--force-with-lease`! +# +# be very careful with force pushes `--force-with-lease` is +# still a potential foot gun! +git push --force-with-lease +``` + + +## Resolving conflicts + +For resolving conflicts you can either use a graphical mergetool, or +decide to do it manually (in the file itself). +I do not use any GUI for Git, so can not give you any +recommendations, or instructions for how to use them. +I will just describe the manual approach, it is no more difficult +really. + +Git will insert searchable sequences around the conflicts and label +both versions with the source of the change. +See the example below. + +``` +<<<<<<< HEAD +hello world +======= +blargh +>>>>>>> f837762 (b) +``` + +To solve a conflict, decide which parts you want to keep and delete +the rest of the changes encased by Gits markers. +If you want or need to you can also mix the changes of both commits. +Then delete the markers and save the file. +Stage the file and continue the merge or rebase with +`git merge --continue` or `git rebase --continue` respectively. + + +## History inspection + +You can inspect the history with `git log`. + +Use `-n ` or just `-` to show only entries. + +Use `--all` to show commits newer than HEAD, other branches and +remote references. + +Use `--oneline` to get a compact view. Combine it with `--graph` to +display the commit tree as ASCII art. + +Use `git show ` to get the diff of the +commit. + + +## History rewriting + +This is an advanced topic not recommended for beginners. +I suggest to get used to work with Git first and come back once the +desire to clean up the history rises. + +The primary tool for history rewriting is the interactive rebase +(`git rebase -i`). +It provides a multitude of functions, reorder, reword and squash to +name just a few. + +Using `git rebase -i ` will open a list +of all commits following the provided commit. +In the development repository for CT for example I used the command +`git rebase -i HEAD~3` to get the following file opened by Git. + +*The '~3' means three commits before HEAD.* + +``` +pick a958dff # continued ct2->lab 05 +pick 52c15fd # elaborated root readme for ct1 +pick 555cf90 # moved and extended `git-introduction.md` + +# Rebase 4abb7c9..555cf90 onto 4abb7c9 (3 commands) +# +# Commands: +# p, pick = use commit +# r, reword = use commit, but edit the commit message +# e, edit = use commit, but stop for amending +# s, squash = use commit, but meld into previous +# commit +# f, fixup [-C | -c] = like "squash" but keep only the +# previous commit's log message, unless -C +# is used, in which case keep only this +# commit's message; -c is same as -C but +# opens the editor +# x, exec = run command (the rest of the line) using +# shell +# b, break = stop here (continue rebase later with 'git rebase +# --continue') +# d, drop = remove commit +# l, label