27 feb 2026
Todays introduction into git features all you need for
daily work.
This lecture covers the most basic git usages like:
clone - copy a repositoryblame - show last change infocommit - save changespush - upload changespull - fetch and mergerebase - integrate changes linearlyreset - undo changesstash - save changes temporarilytag - mark specific pointsworktree - multiple working directoriesbisect - find bugs via binary searchbranch - create/manage branchesrm - remove filesmv - move/rename filessvn - interact with Subversionrevert - undo commitsdiff - show differencessubmodule - use nested repositoriescherry-pick - integrate a commit by commit hashSee https://git-scm.com/docs for detailed description.
Online resources:
links:
Looking at commands which help you to get insight into repositories.
git clone / branch / log / blame / diff
Call this command:
git status - Displays the state of the working
directory and staging area.
What branch are you in?
Show solution
You are in main, this is the default branch set in this repo. The default branch can differ between repositories.
git branch
How to list remote branches?
Show solution
git branch -r will show remote branches, sometimes it
requires a git fetch --all to update the local knowledge
about the remotes.
Change into branch 1_branch_with_many_commits, what is the command to do so?
Show solution
git checkout -b 1_branch_with_many_commits origin/1_branch_with_many_commits
git log - Shows the commit history. Options:
--oneline!
What is the commit hash of the commit with the message: “adding new_file”?
Show solution
d22e0c8 adding new_file
git blame README.md - Shows the file and the
respective editors per line.
The README.md had many changes, identify the user who added the top most fox in the README.md!
Show solution
a1389ad9 (Foxhunt 2026-03-18 14:27:44 +0000 8) 🦊 U+1F98A Fox Face Unicode Character
git diff - Shows changes between commits.
Sometimes you have a branch with many commits but you need to see it as one change to understand the effect it has.
Figure out what happened between commit 1397c7
and 5c33c4 using git diff ...?..., what is
the command?
Show solution
You can see all the changes as one change using
git diff 1397c7..5c33c4.
When you look at the change you realize this: The chicken army got erased by the fox! - oh no!
Github is a very popular platform with 100 million developers both for open source and closed source projects. A lot of todays ‘best practices’ were created there so we’ll use it to learn them.
The diagram below shows a typical GH workflow for how to contribute to a project using git and pull requests (PR).
Create a github account at https://github.com/signup
Mine is https://github.com/qknight
(or) login to GH if you already have an account
Reflection time:
where does the PR appear?
Show solution
If you did it right it should appear at https://github.com/nixcloud/git-training/pulls
is your fork a private or public repo (this is specific to github, not git in general)?
Show solution
By default forking a repo inherits the public/private setting from the parent.
https://github.com/nixcloud/git-training/issues is currently empty but we need to create a “Cucco storm”!
A Personal Access Token (PAT) is the recommended way to work with git from the command line.
Repository access ‘All repositories’
Permissions
Expiration date: A few days
Write down the token:
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We’ll use the PAT later so note it down.
We already cloned git-training using
https://github.com/nixcloud/git-training, we reuse this
here.
git switch main to go back to the main
branch
now we connect to ‘our’ newly crated forked repository on github
git remote add qknightmt https://github.com/qknightMT/git-training
instead of using git remote add ... you can also edit
the .git/config with
nano .git/config.
check the configuration
git remote -v
origin https://github.com/nixcloud/git-training (fetch)
origin https://github.com/nixcloud/git-training (push)
qknightmt https://github.com/qknightMT/git-training (fetch)
qknightmt https://github.com/qknightMT/git-training (push)create a new branch
git checkout -b 0_push_test
Does this create a local or remote branch? What is the difference?
Show solution
It creates a local branch and you need to push it so it becomes also a remote branch. The difference is where the branch ‘lives’.
and push it to your your new remote using
git push qknightmt 0_push_test
Git will ask for your username and password, use your username you created. Mine is:
git push qknightmt 0_push_test
Username for 'https://github.com': qknightmt
Password for 'https://qknightmt@github.com':
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote:
remote: Create a pull request for '0_push_test' on GitHub by visiting:
remote: https://github.com/qknightMT/git-training/pull/new/0_push_test
remote:
To https://github.com/qknightMT/git-training
* [new branch] 0_push_test -> 0_push_testdo the same push again
git push qknightmt 0_push_test
You have to login again, oh no! Enter your credentials again and see this:
git push qknightmt 1_branch_with_many_commits
Everything up-to-datestoring credentials
We do not want to type the user / password each time, so type:
git config --global credential.helper cache
Now push again with user / password:
git push qknightmt 1_branch_with_many_commits
And then do it again, you should not be prompted for another password.
Note: You can flush the credentials cache for testing:
git credential-cache exit
Reflection time:
where else could you store the PAT other than it in the
.git/config url parameter as we just did?
Show solution
The credential helper from git, commands like:
git config --global credential.helper cache
Why are PATs better than username/password for login?
Show solution
You can use them on partially compromised environments and if stolen they only allow access to code and not automatically all your github settings.
Ideally you have PATs for groups of code and not, as we did, one PAT for all the repositories.
Often you need to share a snippet of code or configuration with others. An easy and persistent way is to use github gits for this!
when you open the git-training there is also a gist linked, open it and speculate what it is about!?
Show solution
The link https://gist.github.com/qknight/0231bce88b1b32b4efbcb8e44557cd8c is a documentation how to easily spin up such an environment.
create a short snippet of text
publish snippet as a ‘secret gist’. What makes a secret gist ‘secret’?
Show solution
The secret is basically the url part and if known everyone can read it.
https://gist.github.com/qknight/f342fad470d30a036eb09d7b85c70146
why does it make sense to fork a gist from someone? why does git forking in general make sense?
Show solution
A git repo, similar to a gist can disappear and a fork let’s you archive it for yourself. It helps upstream track popularity and community and you can make modifications to the code.
In communication it is important to talk about the same things, obviously, so:
visit https://github.com/nixcloud/cargo/blob/master-libnix/Cargo.lock
create a link on line 13 (open it in a new tab)
now create a perma link on line 13 (open it in a new tab)
Creating git-repositories is easy and you gonna do this a lot. This section introduces a few different concepts.
There are a few ways to do this:
You create a new repository on the server, then with your client you usually type:
But we already have a clone of this.
You create a local git repository, then you initialize an empty server with it:
create a new directory mkdir my_git_repo and
cd my_git_repo into it
git init - Initializes a new Git repository
Next create an new empty git repo on your server
Finally add the remote (we skip this for now)
git branch -M master
git remote add origin git@github.com:qknight/test.git
git push -u origin masterFrom SVN: git svn clone <repository-url> -
Imports an SVN repository into a new git repository with full
history.
Do this checkout now! How many commits does it have in git, also explain the number?
Show solution
the answer is: it is 6 git commits and the other SVN revisions are probably used for SVN branches.
git commit, add, unadd,
push, push -f and
resetGit introduces more flexible and granular commit handling compared to SVN, allowing selective commits and easy reordering of changes. However, it also necessitates careful management to avoid complex history and conflicts, which can be more challenging than SVN’s straightforward commit system.
git add a_fileAdds a file into the staging area. A following
git commit converts it into git object,
see Git-Objects.
Now do this in branch main:
modify the README.md, add some section with a duck 🦆 or 🐤
call git add README.md
call git commit -m 'Updated README.md'
use tig or git log --oneline to see you
change
next add another section to README.md
call git add README.md and then you notic that we
actually don’t want that section in there!
finally figure out how to undo a git add a_file
(sadly not git unadd). what is the command?
Show solution
git restore --staged a_file is recommended new
defaultgit reset a_file is what i learntafterwards check that git status still shows changes and
that your lines edited are still there. again - we do not want to get
rid of the change but we want it in a different commit.
(optional) if you feel adventurous, try this:
in the README.md add a top section and a bottom section
use git add -p README.md (only add the top
section)
use git commit README.md, what just happened with
the bottom section?
Show solution
git add -p can be used to only commit parts of a file
into the staging area, the not selected parts remain in
the working area.
git push -fSometimes, after a commit & push you find out that you forgot something. You can now add another commit but … sometimes a correct history is important.
Typical scenario:
There are two options now:
you can make the change and use git commit --amend
and git push -f afterwards
When is this a good idea and when to not do it? Research using google or AI!
Show solution
git push -f breaks history. Say you pushed your code and
someone is already working with it you can’t use a force push. Most
often force push is a bad idea.
you can undo your commit and return to the previous commit
Find out how to do this, assume you made just one commit.
Show solution
git reset --hard HEAD^ and then git log and
if happy git push -f. YOU USUALLY NEVER WANT TO DO THAT
Finally figure out what the difference between
git reset HEAD --hard
vs. git reset HEAD --soft.
Note: If you are smart, a better command than
git push -f is git push --force-with-lease.
Why?
git pullA traditional ‘git pull’ uses the merge strategy, which works remarkably well but renders the commit history hard to read, makes rebasing a nightmere.
first clone https://github.com/sumantabose/git-playground
git clone https://github.com/sumantabose/git-playground
cd git-playground
tig
How many merge(s) do you see?
Show solution
With top commit 38922f it should be 6 M merge commits
Are merges commits easy to read?
Show solution
A merge commit (vs. a fast forward) commit adds an additional commit and the interwinding of branches it is hard to follow what is going on.
If possible try to keep git history without merges.
So a typical scenario:
main and start
working.git add files and
git commit -m 'change' and git push.However, dev B needs to do something before pushing:
git fetch --allgit rebasegit pushAn alternative to these 3 commands is:
And call git pull or even better, just always use:
git pull --ff
What does –ff do?
Show solution
Compare to Default
git pull (without –ff): If a fast-forward is
not possible, Git creates a merge commit.git pull --ff, it strictly avoids merge commits
and only fast-forwards if possible.
or
git rebase origin/main
Git provides flexible mechanisms like branching and stashing that allow for non-linear workflows, which is a contrast to SVN’s more linear approach. While these features enhance workflow versatility, they also require more attention and understanding to manage effectively compared to SVN.
Switch to a different local branch:
git switch <branch-name>
How to list local branches?
git branch -l
git stash
1/2Sometimes we made many changes but don’t feel it is good enough for a commit YET we have to checkout something for a collegue.
We similate this now:
git status should show it is an git branch
main with no modificationsgit status to verify changed files!git stashgit status, what changed?git stash memorized the changes on a stack for later
recovery but leaft a clean branch (no changes).
Next find out how to get the files back!
git stash apply on a clean git repo will reapply the
changes.
Try the same command again but this time make a little change to README.md and then try to get your files back. What happened?
git stash apply on a branch with changes errors out so
you don’t clutter your workspace.
Git stash is your friend, if in doubt you can also commit to a working branch and store it on the remote so in case you laptop gets stolen the code it still safe.
git remote -vCompare git remote -v with the content of
cat .git/config. What do you see?
git remote -v only lists remote branches while
.git/config lists all branches and the repo configuration.
A fairly common problem is this: You forked a git repository, someone else did the same and now you need his commits locally:
git remote add qknight https://github.com/qknight/git-training.git
git fetch qknight
git checkout -b flu qknight/4_a_new_feature
git stash
2/2Sometimes one starts to work on the wrong branch, makes changes, even
stages files and then realizes that some patch is missing. Then the
changes need to be moved to the other branch with
git stash.
git status should show it is an git branch
main with no modificationsecho "appending to the end, since the end is near 🐤" >> README.mdgit stashgit switch 1_branch_with_many_commitsThat didn’t work! What do you have to fix in order to make the git stash work?
The git stash is missing the new_file since it was never
ever commited before it is ignored by git stash, we can
simply do a git add new_file and then run git stash.
git stash apply we have to solve a merge conflict on
README.md, depending where you made your change.
git tagA git tag is applied to a commit and not a branch, so do this:
How to upload the tags?
git push --tags.
Note: If you ever set a tag, never, NEVER change it, just add a new one with an increased version number.
git bisect
commandIn this section, the process of pinpointing a fault using git bisect is discussed, which involves marking commits as “good” or “bad” while running python main.py to determine which commit introduced an issue.
We have a branch with many commits but weeks later a fault was
detected running python main.py. For some reason it stopped
working but we don’t know when nor why. We need to find the
commit which caused the issue!
git checkout 2_bisectpython main.py and see if it is
workingWe use git bisect for this. It is controlled with a few
helper commands start, good, bad
and reset.
Which commit introduced the issue? Verify the source code with
python main.py, it must always show
Success, then it is good. Otherwise it is bad!
What needs to be done if one of the commits does not compile and we
can’t decided if it is good or bad?
git bisect skip and this will select a commit
next to the one we skipped so we can continue.
In this section, it is explained how Git allows history rewriting, but it should be done cautiously since it can disrupt others’ work. Key actions like amending the latest commit, merging, squashing, cherry-picking, and reverting commits are demonstrated, with a focus on handling conflicts and making clear and concise commit histories, which are different from SVN practices.
History in git can be rewritten when:
A change like this means that everyone else who works on the same base will have huge trouble pushing his changes later one so use with care.
Use tig to see the history, use
git diff tests..origin/main to see what is going on?
We need to cleanup a few things in this branch:
checkout branch: 3_rebase_commits
see the logs with tig
[nix-shell:~/git-training]$ git log --oneline
00d29f0 (HEAD -> 3_rebase_commits, origin/3_rebase_commits) Updated sensitive_script.py to use environment variables
3f569e0 Adding sensitive_script.py
5b57df2 Adding LICENSE file to repo since i forgot it
3b4586e Updated legal README.md notice
e731eea (origin/main, origin/HEAD, main) Update README.md
5c33c42 Initial commitWhat is the meaning of origin/main, origin/HEAD, main and HEAD?
Show solution
| Reference | Description |
|---|---|
| origin/main | the main branch on the ‘origin’ points to commit e731eea |
| main | the local main branch points to the same commit! |
| origin/HEAD | default branch (like main or master) |
| HEAD | a pointer to the current branch’s latest commit. Used to track branch/commit |
call git rebase -i e731ee
What is e731ee and why does it make sense to call this?
Show solution
e731ee is the commit on the main branch which is the common base to this branch
final push
normally you would do a git push -f and be done with
it
we just store the result like this:
git checkout -b 3_rebase_commits_yourname
git pushSquashing multiple commits into one commit can be very helpful. But when is commit smashing safe?
Yes, using git rebase we loose history but gain
clearity!
So do this:
What do you see?
Show solution
There are many merges between 3 users: UserA, UserB and Joachim. everything is on top of branch ‘main’ which is good!
It looks ugly…
First, we’ll work on a copy on top of that branch so we can always go back easily, how to do this?
Show solution
git checkout 3_wild_merges; git checkout -b 3_wild_merges_rebase
Next, the rebase! Use
git rebase -i e731eea7db433dd7371d54c6b676191f308bd6a8,
smash all the 20 commits into one with “s”:
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c]
But you get an error, what is this error about?
Show solution
git rebase -i e731eea7db433dd7371d54c6b676191f308bd6a8
returns an error.
You can now resolve all these conflicts manually or use a different strategy.
First let’s recap, why did the rebase fail?
Show solution
GPT5: Short answer: rebase replays every commit one-by-one on a new base (and drops/linearizes merges), so conflicts that were previously resolved inside old merge commits reappear during the replay. merge –squash computes one combined tree diff from the branch tip (vs merge-base) and applies it once, so it often avoids those per-commit conflicts.
Ways to achieve your goal and avoid the pain:
If you just want “one commit that equals the branch’s final state on top of main”: git checkout main git merge –squash your-branch git commit -m “squash: your-branch” If you must rebase but want to auto-resolve conflicts by preferring the branch’s changes: git checkout your-branch git rebase -X theirs main then merge or push as needed If merges matter and you want to preserve them during rebase: git rebase –rebase-merges -X theirs main Enable rerere to auto-reuse recorded conflict resolutions across attempts: git config –global rerere.enabled true git config –global rerere.autoupdate true Rule of thumb: for “final tree = tip-of-branch, single commit,” prefer merge –squash (or a soft-reset-based squash). Use rebase only if you need a linearized history of individual commits.
See https://www.reddit.com/r/git/comments/p9x33m/linearize_a_git_history_wo_merge_conflicts_to/!
This is the easy and recommended alternative to the problem above:
git checkout main
git checkout -b 3_wild_merges_rebase_squashed
git merge --squash 3_wild_merges_rebase
is the better option.
Main lecture: Don’t use traditional merges, use rebases on pull. It makes history reading easier, it makes git bisect easier. It sometimes makes merges harder though…
git cherry-pickfind out what commit fb8cb9 is all about
Show solution
git show fb8cb9 displays the change which updates the
LICENSE file content.
checkout the branch: 3_revert_cherry_pick
Show solution
git checkout origin/3_revert_cherry_pick -b 3_revert_cherry_pick
git cherry-pick fb8cb9
This resulted in an error. What is this about and how to resolve it?
Show solution
We don’t have a LICENSE file yet and the commit is a ‘change’ thus it fails. As a solution we have to add it.
How to add it? git status shows we are still in a
cherry-pick state, we could exit using
git cherry-pick --abort but we don’t want to! fix the
cherry pick. what are the commands?
Show solution
We have to continue and finish the cherry-pick. Don’t just do a
git commit!
git add LICENSE git cherry-pick --continue
git revertcreate a new branch 3_revert_commit from origin/3_wild_merges
git checkout -b 3_revert_commit origin/3_wild_merges
use git revert on revert commit
5c33c4. What is the problem and how did you solve
it?
Show solution
The initial commit 5c33c4 added the README.md and running
git revert 5c33c4 results in the conflict that the next
commit modifies the README.md. Since we don’t want the README.md to ever
have existed, we do a git rm README.md and then a
git revert --continue to finish the operation.
Compare the git log --oneline to
git log origin/3_wild_merges --oneline, what is the
difference?
This section discusses the use of git submodule for managing dependencies in projects lacking package managers, such as C/C++ projects, whereas modern languages like Rust use dependency files such as Cargo.toml.
The command “git submodule” is used for languages which don’t have a package manager like c / c++ / qt. In modern languages like rust (cargo) this is not used anymore and modified dependencies are integrated using Cargo.toml instead on the source code level with ‘git submodule’.
checkout branch 7_submodule, type
git branch, what does it show?
Show solution
7_submodule is green as you are in that branch
run the program: python main.py, what do you
see?
Show solution
python main.py
Error: submodule/README.md does not exist
next we need to add the required submodule, please add https://github.com/nixcloud/git-training with branch main into the folder ./submodule! What is the command?
Show solution
git submodule add https://github.com/nixcloud/git-training submodule
after adding the submodule, run it again, what does it say now?
Show solution
python main.py
Number of chickens: 1
it added the branch main but we want 1_branch_with_many_commits! how do we get that?
Show solution
We need to go into the ./submodule directory and checkout a different revision…
pushd submodule
git branch -r
git fetch --all
git checkout origin/1_branch_with_many_commits
popd
git add submodule
git status
[nix-shell:~/git-training]$ git status
On branch 7_submodule
Your branch is up to date with 'origin/7_submodule'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: .gitmodules
new file: submoduleIn essence we have a git repository inside a git repository where the
outer manages the inner on with commands like
git submodule update --init.
observe the difference between git status in
directory git-training/ and
git-training/submodule
Show solution
You see that the git program in both directories returns different
results. This is unusual! If git is called from a
subdirectory inside a git repository it usually traverses upwards to the
first .git/config directory and assumes this is the parent.
Git submodules break with this assumption, see .gitmodules
in the main repo for instance.
[nix-shell:~/git-training]$ git status
On branch 7_submodule
Your branch is up to date with 'origin/7_submodule'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: .gitmodules
new file: submodule
[nix-shell:~/git-training/submodule]$ git status
HEAD detached at origin/1_branch_with_many_commits
nothing to commit, working tree cleanFinally, after we added branch 7_submodule in
the step before, run python main.py again, what does it say
now?
Show solution
python main.py
Number of chickens: 4
Congratulations, you made it!
Note: The example uses ./submodule as folder-name but you can use whatever you like.
Do you like to work with git submodules?
The correct answer is NO.
Yet we have to, but it makes everything a pain. Try to use HTTPS urls over SSH urls as the build nodes might only have access to the HTTPS checkout.
Complicated git merges/rebases sometimes make you pick the wrong git submodule update resulting in adventurous scenarios.This section highlights the lack of structured namespaces or organizational structures in certain Git workflows compared to platforms like GitHub. Consequently, pull requests (PRs) are handled differently, focusing purely on repositories and branches without the typical user-centric organization.
This section enumerates key elements of modern version control practices, including forking and creating pull requests (PRs), managing issues using markdown, maintaining documentation with README.md and LICENSE files, and the different branching models such as Gitflow, GitHub Flow, and GitLab Flow.
This section provides insights into useful tools and settings for version control. It suggests using tig for staging files with specific keystrokes, recommends IntelliJ for its robust Git integration, especially for complex rebases, and mentions meld as a great open-source 3-way merge tool. Additionally, it advises on proper line-ending settings during Git installation on Windows to maintain consistency with Unix-style line endings.
tig how to use this tool to add files from the
working area into the staging area
(like git add a_file)?
intellij best git integration, see https://www.jetbrains.com/idea/
if you have to do complex rebases, this is the editor! the 3-way-merge is excellent.
meld best open source 3-way merger, see https://meldmerge.org/
integrates into GIT using the command git mergetool but
is much harder to integrate.
Line endings encoding Windows vs. Linux
When installing git-scm on windows, select this:
[x] Checkout Windows-style, commit Unix-stle line endings
[ ] Checkout as-is, commit Unix-style line endings
[ ] Checkout as-is, commit as-isWith all these lectures done you have a solid education how to work with GIT and replace SVN where possible.