- Add files and directories to a mysterious **index**
- a.k.a. **staging area**
- Empty folders will be ignored
- Prevent this by adding an empty, hidden file, e.g. ".gitkeep"
Technically, you add _changes_, like the change of adding a file. Or editing a file. Even the change of removing a file.
---
## Committing
Commit: a set of previously added, related changes
git commit -m "fix server crash, resolve #5" # shortcut, only header
git commit # extended version, editor opens, enter header and message
Commit changes to the local repository
Referenced bugs will be closed on Git{Hub,Lab}, if pushed to the upstream repository!
Use any of close #5, closes #5, closed #5, fix #5, fixes #5, fixed #5, resolve #5, resolves #5, resolved #5
---
### Commit Style
- Write meaningful commit messages
- Use imperative: change config, remove file, add tests
- Header should be 50 chars or less, message can be longer
- Make atomic commits![1]
- Commits can span multiple files, but should have _one_ function, like
- Add tests to module xyz
- Use new house-style
- Introduce logging
---
## The 5 Holy Realms of git
workspace 📂
Your filesystem
index 🕒
Staging area
Files wait patiently to be committed
local repository 🗂️
Contains branches, commits, history, etc.
upstream repository ☁️
Cloud or self-hostet Git{Hub,Lab}
Shared folder
stash 📦
Temporary storage area
Notes:
Most important slide of today
Upstream Repo in a minute
# Everyday git
---
## Status
$: git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: 00_title.md
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)
modified: index.html
deleted: slides/environments/reveal.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
slides/advanced_git/
- Which files are staged (to be commited)?
- Which files are changed, but not staged?
- Which files are not tracked?
- You will use this very often!
---
## (Re)move Stuff
rm file.txt # delete files from your WORKSPACE
mv file_2.txt file_3.txt # move or rename files in your WORKSPACE
rm file.txt # delete files from your WORKSPACE
mv file_2.txt file_3.txt # move files in your WORKSPACE
git add file.txt # ... and your INDEX
git add file_2.txt file_3.txt # ... and your INDEX
git rm file.txt # Shorthand for rm X & git add X
git mv file_2.txt file_3.txt # Shorthand for mv X & git add X
- "git add" does not add files to the **index**, but _changes_
- The _change_ of removing a file from the **workspace** must be _added_ to the **index**
---
## Ignore stuff
.gitignore is a file in the root of your project
$: cat .gitignore
# don't track passwords.txt
passwords.txt
# ignore *.tmp files, notice that wildcards '*' work
*.tmp
# but _do_ keep track of important.tmp
!important.tmp
# ignore 'logs' directories _anywhere_
**/logs
# ignore debug-a.log ... debug-z.log
debug-[a-z].log
- Hide certain files from git
- They stay in your **workspace** ...
- but out of **index**, **local repository**, **upstream repository**
- Examples: Passwords or API tokens, log files or temporary files
---
### External Repos
# Get repo from an UPSTREAM REPOSITORY, creating a LOCAL REPOSITORY
git clone https://gitlab.com:OleMussmann/my_project.git # or...
git clone git@gitlab.com:OleMussmann/my_project.git
# Get latest updates from the UPSTREAM REPOSITORY, and merge them
git pull git@gitlab.com:OleMussmann/my_project.git
# Which is shorthand for:
# Get latest updates from the UPSTREAM REPOSITORY
git fetch git@gitlab.com:OleMussmann/my_project.git
# Merge the remote changes to your local branch
git merge <current_branch> <origin/current_branch>
# Push changes from your LOCAL REPOSITORY to the UPSTREAM REPOSITORY
git push git@gitlab.com:OleMussmann/my_project.git
- Always "git pull" before working on a shared repo!
- Changes in both **local repository** and **upstream repository** introduces _merge conflicts_
---
## Branches 1/2
![Branches](./files/branches_1.svg)
---
## Branches 1/2
![Branches](./files/branches_2.svg)
---
## Branches 1/2
![Branches](./files/branches_3.svg)
---
## Branches 1/2
![Branches](./files/branches_4.svg)
---
## Branches 1/2
![Branches](./files/branches_5.svg)
---
## Branches 2/2
$: git branch # list existing branches
existing_branch
* main
old_branch
$: git switch existing_branch # switch to an existing branch
$: git branch
* existing_branch
main
old_branch
$: git switch -c new_feature_branch # c_reate (and switch to) branch
$: git branch
existing_branch
main
* new_feature_branch
old_branch
$: git branch -d old_branch # d_elete a branch
$: git branch
existing_branch
main
* new_feature_branch
# new branches have to be explicitly pushed to a remote:
$: git push -u origin new_feature_branch # u_pstream: origin
origin is an alias for the UPSTREAM REPOSITORY URL
---
## Branch Style
- Use "main" instead of "master" for your main branch
- Don't commit directly to "main"
- "main" will be what users will clone and should be stable
- Use a "development" branch for daily work
- Merge to "main", once stable
- Use feature branches for developing features
- Merge to "development"
- Use descriptive branch names
---
## Detached HEAD 🤕
$:
"v1.0"
|
main (m1)--(m2)--(m3)
|
HEAD # default, HEAD points to the tip of "main"
- Usually HEAD is a pointer to the tip of a branch
---
## Detached HEAD 🤕
$: git switch --detach v1.0 # notice the mandatory --detach flag
"v1.0"
|
main (m1)--(m2)--(m3)
|
HEAD (detached) # sounds worse than it is
- Usually HEAD is a pointer to the tip of a branch
- But when switching to commit hashes or tags, the HEAD is "detached"
"v1.0"
|
main (m1)--(m2)--(m3)
\
(x1) # ephemeral, "virtual" branch
|
HEAD (detached)
- Usually HEAD is a pointer to the tip of a branch
- But when switching to commit hashes or tags, the HEAD is "detached"
- Edits that you make now would be lost
"v1.0"
|
main (m1)--(m2)--(m3)
\
(x1)--(x2) # ephemeral, "virtual" branch
|
HEAD (detached)
- Usually HEAD is a pointer to the tip of a branch
- But when switching to commit hashes or tags, the HEAD is "detached"
- Edits that you make now would be lost
"v1.0"
|
main (m1)--(m2)--(m3)
\
new_branch (n1)--(n2) # proper branch, will go down in history
|
HEAD
- Usually HEAD is a pointer to the tip of a branch
- But when switching to commit hashes or tags, the HEAD is "detached"
- Edits that you make now would be lost
- Creating a new branch re-attaches the HEAD
---
## Merge
git switch -c new_feature_branch # c_reate (and switch to) new branch
# do things on new_branch, like adding and committing
git switch main # switch to target branch
git merge new_feature_branch # merge new_feature_branch into main
- git v2.23 instroduces 'git switch' and 'git restore'
- They replace 'git checkout'
---
## Tags 1/2
#### Label your commits 🏷️
![Branches](./files/branches_5.svg)
---
## Tags 1/2
#### Label your commits 🏷️
![Branches](./files/branches_all.svg)
---
## Tags 2/2
$: git tag # show all tags
v1.0
v1.1
$: git tag -a v1.2 -m "new gui" # a_nnotated tag with a m_essage
$: git tag found_bug_in_this_commit # lightweight tag: bookmarks
$: git push origin v1.2 # push a single tag
$: git push --tags # or push all tags
$: git switch --detach v1.0 # detach head and switch to a tag
- Annotated tags contain lots of metadata
Use them for actual tagging
- Lightweight tags are bookmarks
Use them as personal bookmarks
- You can switch to tags, [detaching the HEAD](#/detached)
# Advanced git
---
## Advanced Status 1/2
Differences
git diff file.txt # WORKSPACE - INDEX
git diff <commit> file.txt # WORKSPACE - COMMIT HASH
git diff HEAD file.txt # WORKSPACE - LOCAL REPO (COMMIT H. = HEAD)
git diff --staged file.txt # INDEX - LOCAL REPO (HEAD)
git diff --staged <commit> file.txt # INDEX - COMMIT HASH
$: git diff # Omit filename to show _all_ changed files
diff --git a/file.txt b/file.txt
index 00dedf6..02ef113 100644
--- a/file.txt # old version
+++ b/file.txt # new version
@@ -1 +1 @@ # one line changed
-abcde # removed lines
+new line here! # added lines
---
## Advanced Status 2/2
Commit history
# try tab completion, a file name or a commit hash!
$: git log
# HEAD points to the latest commit in the LOCAL REPOSITORY
commit 0fb599d96521bd27b05b0a6f963ba6ab903d349c (HEAD -> main)
Author: ole <gitlab+account@ole.mn>
Date: Mon Jun 13 11:07:30 2022 +0200
add content to README
commit 89d8a1b57f2440693aff2f7b5003201836aa24d4
Author: ole <gitlab+account@ole.mn>
Date: Mon Jun 13 11:06:51 2022 +0200
create README.md
Show commits
# try tab completion, a branch, tag or a commit hash!
$: git show 0fb5 # four hash characters are enough
commit 0fb599d96521bd27b05b0a6f963[...] (HEAD -> main)
Author: ole <gitlab+account@ole.mn>
Date: Mon Jun 13 11:07:30 2022 +0200
add content to README
diff --git a/README.md b/README.md
index e69de29..8824ebc 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,3 @@
+Hi! I am a README file.
+
+I have content.
Blaming!
(... or "who changed a file"?)
# try tab completion or a file name!
$: git blame file.txt
e546a240 (ole 2022-06-10 11:12:27 +0200 1) new line here!
682bc7ca (ole 2022-06-10 11:19:20 +0200 2) and another one
a9a390b3 (ole 2022-06-10 11:20:05 +0200 3) many
a9a390b3 (ole 2022-06-10 11:20:05 +0200 4) more lines
---
## Stash 1/2
#### Putting things away temporarily
$: git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: staged_file
Untracked files:
(use "git add <file>..." to include in what will be committed)
workspace_file
$: git stash -u # Optional -u to include u_ntracked files
Saved working directory and index state WIP on main: [...]
$: git status
On branch main
nothing to commit, working tree clean
---
## Stash 2/2
#### Retrieving things
$: git status
On branch main
nothing to commit, working tree clean
$: git stash list
stash@{0}: WIP on main: 50f8199 add committed_file # only one item
$: git stash apply stash@\{0\} # Omit stash name for latest
$: git stash apply # Use latest stash
$: git stash apply --index # Optional --index to also apply INDEX
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: staged_file
Untracked files:
(use "git add <file>..." to include in what will be committed)
workspace_file
Too many changes since the stashing might might create [merge conflicts](#/conflicts). You can make a branch instead and work from there:
# Omit stash name for latest
$: git stash branch new_stash_branch stash@\{0\}
---
## Merge Conflicts
- Merge conflicts occur when you try to merge a branch/stash that conflicts with your local version, e.g.
- Overlapping edits in the same file (obvious)
- Win vs Linux file endings, tabs to spaces (invisible)
1. Don't panic! You can always abort the merge
1. Git gives you helpful error messages and the tools to resolve the conflicts
1. You are not the first one to encounter that specific error, google and stackoverflow are your friend
---
## Redneck Style
Don't tell anyone you've got this from me... 🙊 But this is a totally viable strategy. Here we go.
Do a fresh git clone into a separate folder
Work in your changes
Push your changes
Delete your original folder (optional)
---
## By Hand
$: git pull # Oh no, a merge conflict! (Don't panic, I've got this.)
[...]
From https://renkulab.io/gitlab/renkuaccount/git
c297618..09f2cc8 main -> origin/main
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.
$: git status # git status shows me what's wrong
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours) # Not so helpful, it failed
You have unmerged paths.
(fix conflicts and run "git commit") # Helpful!
(use "git merge --abort" to abort the merge) # Panic button, good to know
Unmerged paths:
(use "git add <file>..." to mark resolution) # git assumes i'm happy after 'add'
both modified: file.txt
no changes added to commit (use "git add" and/or "git commit -a")
$: git diff # git diff shows me affected lines (phew, does not look too bad 😅)
diff --cc file.txt
index 2054524,f15ccdf..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,5 -1,5 +1,9 @@@ # merged lines 1-5 ("file a"), 1-5 ("file b") to lines 1-9 ("file c") ?!
text text
all the same everywhere
++<<<<<<< HEAD # Diff start
+line edited locally # Part from HEAD ("file a")
++======= # Separator
+ line edited on remote # Part from remote ("file b") (commit hash 09f2c[...])
++>>>>>>> 09f2cc8e449905494d3ca61d78c5b071d62db1cf # Diff end
more same lines
no conflicts down here
$: cat file.txt # Looking at the file.txt content. Aha! This is "file c"
text text
all the same everywhere
<<<<<<< HEAD # I should remove these control lines
line edited locally # I can keep this line ...
======= # (remove)
line edited on remote # ... or this line. In fact, I can edit the file however I want!
>>>>>>> 09f2cc8e449905494d3ca61d78c5b071d62db1cf # (remove)
more same lines
no conflicts down here
$: [edit file.txt, fix conflicting lines, remove control lines]
$: git add file.txt
$: git commit -m "resolve merge conflict"
[main 0835b22] resolve merge conflict
$: git push # All clear! 😃
[...]
---
## With a mergetool: vimdiff
git mergetool --tool=vimdiff
- Also show LOCAL, BASE (best common ancestor), REMOTE
- vim is about everywhere
- Difficult to learn, hard to master, worth knowing the basics!
---
## With a mergetool: meld
git mergetool --tool=meld
- Merge to "best common ancestor" (middle)
- GUI tools like meld are more intuitive
- Not available in CLI environments
# Turn Back Time
---
## Undo 1/4: Commit One More
# No need to commit yet again! Make amends instead.
git commit -m "this should be all files"
git add dang_i_forgot_this_one.txt
git commit --amend -m "this should be all files" # same commit!
---
## Undo 2/4: Restore to WORKSPACE
$: ls
EDIT.txt
DELETE.txt
$: git add .
$: git commit -m "add files"
$: rm DELETE.txt # remove from WORKSPACE but not INDEX!
$: ls
EDIT.txt
# edit EDIT.txt
$: git status
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: DELETE.txt
modified: EDIT.txt
$: git restore EDIT.txt
$: git restore DELETE.txt
$: ls
DELETE.txt EDIT.txt
You can even "restore" files from another branch or the stash
$: git status
[...]
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: SENSITIVE_PASSWORDS.txt # Uh oh!
new file: README.md
$: git rm --cached SENSITIVE_PASSWORDS.txt
rm 'PASSWORDS.txt'
$: git status
[...]
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
SENSITIVE_PASSWORDS.txt
- git rm --cached <file> remove from INDEX
- git restore --staged <file> copy from HEAD to INDEX
---
## Undo 4/4: Rewriting history 📜
**⚠️ WARNING! ⚠️** You are playing with fire 🔥
- This is especially dangerous if other people based their work on your commits.
- It's possible to do it with only git commands ...
- ... but it's safer to use third-party tools, like git-filter-repo
- https://github.com/newren/git-filter-repo/
- Use it to remove accidentally pushed passwords or API keys
Summary
Yes, git has a learning curve
Master the basics, learn the rest as you go
Make use of extensive, free resources
Let git enable your own work and collaborations
# Resources
- _The_ authority, sleep with it under your pillow
- https://git-scm.com/book/en/v2
- Interactive cheatsheet, which commands to use where
- https://ndpsoftware.com/git-cheatsheet.html
- Rebase explained with ... music
- https://www.youtube.com/watch?v=S9Do2p4PwtE
# Exercise
---
### Nano, Command Line Editor 🤏
- CTRL+O, ENTER to write to file
- CTRL+X to exit
---
### Zero to git: Local 👈
**Play with git on mybinder 🏑**
- and wait for it to start
- Configure git to your liking, set at least `user.name` and `user.email`
- And set `pull.rebase false`
- Create a folder **git** and change into it: `mkdir git; cd git`
- Initialize a repo with trunk (main branch) _main_
- Create a file **README.md** and fill it with some markdown, this is in your WORKSPACE
- Add the file to your INDEX
- Commit the file to your LOCAL REPOSITORY
- Name the commit "Initial Commit"
---
### Zero to git: Remote 🖩
**Connect with the World 🌍**
- Create an _empty_ repository on GitHub or GitLab
- Create SSH keys in your binder environment
- Feel free to omit the password _for this exercise only_
- Upload the SSH keys to GitHub or GitLab
- Test the SSH connection
- Add the new UPSTREAM REPOSITORY as remote named _origin_ to your local repo
- Push the main branch to the UPSTREAM REPOSITORY _origin_
- Check in the browser if the **README.md** arrived
Congratulations! 🎉 You have mastered the basics of git.
---
### Everyday git: Back and Forth ↔️
**Understanding the 5 Realms 🏰**
- ⚠️ After each of the following steps, check the status of the repo: `git status`
- Create a few files
- **committed.txt staged.txt edited.txt new.txt**
- Add and commit **committed.txt** and **edited.txt**
- Add (but not commit!) **staged.txt**
- Remove **staged.txt** from WORKSPACE and INDEX
- Create a file **.gitignore** and use it to exclude the file **new.txt**
- Add and commit **.gitignore** and all the other files and push your LOCAL REPOSITORY to the REMOTE REPOSITORY
- Check in the browser the state of your REMOTE REPOSITORY
- Delete all **\*.txt** files, add the changes, and commit your changes to _main_
- Push your changes to the REMOTE REPOSITORY
- Check the status with `git status`
- Create a file **from_remote.txt** on github.com, commit directly to _main_
- Pull the latest changes from the REMOTE REPOSITORY
- Inspect the local files with `ls -a`
- Check the status with `git status`
---
### Everyday git: Branching Out 🎋
**Multi-dimensional ✨**
- Create and switch to a development branch called _development_
- Create, add to INDEX and commit a file **dev_stuff.txt**, name the commit "add dev stuff"
- Realize you were not done yet, create a file **more_dev_stuff.txt**
- Add it to INDEX and commit it _to the same commit_ as **dev_stuff.txt** (make amends)
- Name the commit "add dev stuff" as well
- Confirm that you have only one commit with `git log`
- "Accidentally" delete **dev_stuff.txt**
- Don't worry, it's still saved in the LOCAL REPOSITORY! Restore it back to your WORKSPACE
- Create and add to INDEX a file **passwords.txt**
- Realize it should not end up in the repo, and un-stage it from INDEX
- Add it to the **.gitignore** file, just to be safe
- Add all your changes and commit them
- Switch back to branch _main_
- Merge the branch _development_ into _main_
- If successful, tag the latest commit with an annotated tag "v1.0"
- Push the new branch and the tag to your REMOTE REPOSITORY _origin_
Yesss! 🥳 You are getting good at this.
---
### Advanced git: Stashing Things 📦
**Temporarily Cleaning Up 🧽**
- Create a **file.txt**, and fill it with the line "I'm in the REPO"
- Add it to INDEX and commit to your LOCAL REPOSITORY
- Edit the content of **file.txt** to "And now I'm in INDEX"
- Add the file to INDEX
- Edit the file again and change the content to "Or in WORKSPACE?"
- The file has now three different contents in three different places 😮
- Apply the `git diff` commands that show the differences of **file.txt** in
- WORKSPACE ↔ INDEX
- WORKSPACE ↔ LOCAL REPO
- INDEX ↔ LOCAL REPO
- See what files you have now: `ls -a`
- Check the state of your local repo: `git status`
- Stash the mess you just created, including the untracked (not staged in INDEX) files
- See again what files you have now: `ls -a`
- Check again the state of your local repo: `git status`
- Re-apply the stash, _including the INDEX_
- Confirm that you have everything back, WORKSPACE, INDEX
---
### Advanced git: Gaining Insight 👓
**Take a breather and study what you did 😌**
- Study what you have done in the repo so far: `git log`
- See what's inside the tag "v1.0": `git show v1.0`
- Find out (and blame) who made a mess with **file.txt**: `git blame file.txt`
- Change, add and commit file.txt a few more times and do it again
- Also check with `git log file.txt`
- See the last change of **file.txt**: `git show file.txt`
Take a break (if you haven't yet), get a coffee ☕ and prepare for the finale
---
### git Enlightenment: Conflict Resolution 🕊️
**The Final Frontier 🚀**
- Switch to the _main_ branch, if you didn't already
- Add, commit and push all changes, start with a clean LOCAL REPOSITORY
- Create, add and commit a file **poem.txt**, containing my bad poem
- Push the LOCAL REPOSITORY to your REMOTE REPOSITORY _origin_
- Fix the poem in the browser in your REMOTE REPOSITORY, commit to _main_
- Fix the poem _differently_ in your LOCAL REPOSITORY, add and commit to _main_
- Pull the remote changes
- Resolve the merge conflict
- The mergetool "vimdiff" uses CTRL-W to switch between panes, this collides with the browser shortcut to close windows; resolve the conflict "by hand" instead
Amazing! You made it. 🏅 You probably fought with JupyterLab, wrestled git, fixed unexpected errors, and still pulled through. You are ready to use git effectively in your own projects.
Keep learning and enjoy the journey.