Git
Why do I need version control?
As researchers, you (probably) want:
- reproducible research
- fast and efficient research
- digital files that make sense
Version control systems allow you to keep the history of what you've developed for easy tracking if you actually develop the habit of using git. I've been burnt a few too many times by overwriting some program that works and wishing I had saved it before moving on to the next problem and messing everything up.
The basic git workflow is:
- modify files in your working directory.
- stage files you’ve worked on. This prepares a snapshot of the directory
- commit the files you’ve staged. This stores that snapshot in the Git repository.
Letting git know who you are
Before continuing let’s configure git. You will have to do this once per computer you use git config --global:
git config --global user.name "Vishakh Pradeep Kumar"
git config --global user.email grokkingstuff@gmail.com
git config --global core.editor vim
git config color.ui auto
The --global option corresponds to a user-wide configuration. The configuration will be stored in a hidden repository in your home. You can also configure each git repository individually, by removing this option, or system-wide by replacing the --global option with --system. Usually, we just configure repositories user-wide.
You can check your configuration with:
git config --list
If you’ve configured git several times at different levels, you will probably see several entries twice or three times in the configuration list. The user-wide configuration overrides the system-wide configuration, and the local configuration overrides the user-wide configuration.
My .gitconfig file
[user]
name = Vishakh Pradeep Kumar
email = grokkingstuff@gmail.com
[color]
ui = true
[core]
pager = less -R
compression = 6
[rerere]
enabled = 1
[diff]
mnemonicprefix = true
tool = kdiff3
[merge]
tool = kdiff3
[gui]
editor = gvim
[guitool "edit"]
cmd = gvim -f $FILENAME
noconsole = yes
needsfile = yes
[guitool "mergetool"]
cmd = git mergetool -y -t kdiff3 $FILENAME
noconsole = yes
needsfile = yes
[push]
default = matching
[mergetool]
keepBackup = false
# this is the most commonly used one, it turns out!
fancy_log = log --graph --boundary '--format=%Cblue%h%Creset %Cgreen%ar%Creset %Cblue%d%Creset %s' --all -19
# this is so frequent for me...
pom = push origin master
# pushall = "!f() { git remote | map -p git push; }; f"
pall = !git remote | map -p git push
rl = reflog show --date=relative
ru = remote update
Initializing repositories and configuring git
To initialize a new project, in the project directory, initialize the git repository with:
git init
The second way is:
git clone https://github.com/git-lectures/git-lecture-notes.git
This will clone the repository containing the documents of this tutorial.
Git
Rules
- Version control all projects. Commit often. Squash branches into one good commit.
- Respect existing git workflow of projects.
- Prefix branch names with fix/ or feat/ appropriately.
Josh, Glitter Lazygit seem neat. Think Like Git & What made you finally grok Git? are great reads.
Tools to look at:
Take the changes you've made and automagically identify which commits are safe to modify and which staged changes belong to each of those commits. Might go against the idea that a git's history should be unchangeable, but I guess this only modifies the last few things?
Great resources
- Hacker's Guide to Git,
- Advanced Git: Graphs, Hashes, and Compression
- Inside the Hidden Git Folder
- Think like Git
- What made you finally grok Git?
Code
# set new git remote origin (https://stackoverflow.com/questions/16330404/how-to-remove-remote-origin-from-a-git-repository)
git remote set-url origin git://new.url.here
# Cleanup .git http://gcc.gnu.org/ml/gcc/2007-12/msg00165.html
git repack -a -d --depth=250 --window=250
# Reset to previous commit
git reset HEAD~
# Reset to commit
git reset <commit hash> --hard
# Checkout previous commit
git checkout HEAD~
# Create new branch
git checkout -b
# Revert changes to modified files (working changes)
git reset --hard
# New branch without git history & files
git checkout --orphan
# Show where git configs get defined
git config --show-origin -l
# Undo last commit but don't throw away changes
git reset --soft HEAD^
# List all git submodules
git submodule--helper list
# Pull from PR
git pull origin pull/<issue ID>/head
# List remote branches
git branch -a
# Delete branch
git branch -d
# Delete remote branch
git push origin --delete
# Force overwrite git repo. https://stackoverflow.com/questions/10510462/force-git-push-to-overwrite-remote-files
git push -f <remote> <branch>
# Reset to specific commit
git reset --hard <commit>
# Remove dir from git
git rm --cached -r <dir>
# Rename previous commit
git commit --amend
# Force push overwrite
git push --force origin master
# Hard reset a branch
git reset --hard <branch-name>
# Change remote. i.e. when making a fork of a clone to change upstream destination.
git remote rename origin upstream; git remote rename nikitavoloboev origin
# Change upstream to my name so it pushes there
git branch --set-upstream-to nikitavoloboev/master master
# Show changes between commits. Where f5352 and be73 are unique commit hashes.
git diff f5352 be73
# Update submodule
git submodule update
# Set PGP key for Git globally. <key> = fingerprint w/o spaces
git config --global user.signingkey <key>
# Clone git repo without history (much faster)
git clone [REPO] --depth 1
# Move your unstaged changes to a new branch (https://twitter.com/wesbos/status/1479129404500594691)
git switch -c <branch-name>
# Remove sensitive info committed (https://github.com/tj/git-extras/blob/master/Commands.md#git-obliterate)
git obliterate <>
Notes
- Commit as often as you can. Ideally after each micro-iteration, when something new is working.
- This way, at the end of the day you can just rebase the whole branch and squash all of the micro-commits in a whole commit implementing the whole new features.
- Good git workflow to make changes to new projects: clone, fork (hub fork), 'git checkout -b my-feature', work, commit, 'git push -u nikitavoloboev my-feature', work, commit, 'git push'.
- If you’re doing things right, there’s only two kinds of branches anyways, master and feature branches. Feature branches can be squashed and rebased off master (minimizing the issue of merge conflicts and making for easier management of the commit history) and merged to master from there without requiring further conflict resolution. (Trunk-Based Development)
- A Git branch is just a pointer to a commit. Git commits, however, also contain the hash of the parent commit(s), so by referring to that commit you also refer too all ancestors.
- Squash + rebase (for feature branches) are good for PRs. No one cares that it took you 20 tries to get the feature right, what matters is what went into the pull request, which is usually one commit.