# Practical git

Ole Mussmann | eScience Center |
## Table of Contents
# Why Learn CLI git? - All the power! ⚡ - No abstraction layer 🧅 - If you know CLI git, you can figure out GUIs 💡
# First Steps --- ## Config

# Mandatory (for anything more than clone)
git config --global user.name "Your Full Name"
git config --global user.email your@email.com

# Recommended
git config --global init.defaultBranch main

# Optional
git config --global merge.tool "meld"  # see [1] for options
git config --global core.editor "vim"  # see [2] for options

# List all set options
git config --list --show-origin
Extensive config options:
https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_git_config --- ## New Repo

    git init                        # or ...
    git init --initial-branch=main  # if init.defaultBranch is not set
- Creates a hidden `.git` folder - The current folder is now your **workspace** --- ## Add Stuff

    git add file
    git add folder
    git add *.txt
    git add .
- 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
[1] atomic: a single irreducible unit or component in a larger system
--- ## 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"


--- ## Detached HEAD 🤕

$: git switch --detach v1.0  # notice the mandatory --detach flag
$: edit files; git add .; git commit -m "edited stuff";
 
 

          "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

--- ## Detached HEAD 🤕

$: git switch --detach v1.0  # notice the mandatory --detach flag
$: edit files; git add .; git commit -m "edited stuff";
$: edit more; git add .; git commit -m "edited more";
 

          "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

--- ## Detached HEAD 🤕

$: git switch --detach v1.0  # notice the mandatory --detach flag
$: edit files; git add .; git commit -m "edited stuff";
$: edit more; git add .; git commit -m "edited more";
$: git switch -c "new_branch"

          "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 restore --source stash_name/branch_name filename
--- ## Undo 3/4: Un-stage from INDEX

$: 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.