Version Control#
Table of Contents#
Git#
Getting Started#
Want to get good at using Git and GitHub? Read the following books; work through the following resources; and practice on in your development environment along the way!
git help tutorial
git help tutorial-2
git help everyday
[ d ] Everday Git
[ d ] GitHub’s Git Guides
[ book ] Chacon, Scott & Ben Straub. (2014). Pro Git: Everything You Need to Know About Git, 2nd Ed. Apress.
[ book ] Ponuthorai, Prem Kumar & Jon Loeliger. (2022). Version Control with Git: Powerful Tools and Techniques for Collaborative Software Development. 3rd Ed. O’Reilly.
Installation#
Git for Windows
Git is installed at the following default location.
C:\Program Files\Git
“Add ‘open with code action’ to Windows Explorer File Context menu” adds an option to the right-button-menu in the file explorer to open a file directly with Visual Studio Code (with this option you can open Visual Studio Code directly from a file.).
Configuring git#
Determine your version of git.
git version
or
git -v
or
git --version
Set your name and email.
git config --global user.name "<name>"
git config --global user.email "<email>"
View your configuration settings.
git config -l
or
git config --list
Use the following variant for more information about your configuration.
git config -l --show-scope --show-origin
The location of your config files depends on your system.
./.git/config (local)
~/.gitconfig (global)
/etc/gitconfig (system)
./.git/config (local)
~/.gitconfig (global)
/etc/gitconfig (system)
./.git/config (local)
C:\users\<user>\.gitconfig (global)
C:\Program Files\Git\etc\gitconfig (system)
Configuring git some more#
The following instruction set is based on these documents.
Set vim as git’s default text editor.
git config --global core.editor "vim"
Set VS Code as git’s default text editor.
git config --global core.editor "code --wait"
Configure Git to ensure line endings in files you checkout are correct for macOS.
git config --global core.autocrlf input
Configure Git to ensure line endings in files you checkout are correct for Windows. For compatibility, line endings are converted to Unix style when you commit files.
git config --global core.autocrlf true
Creating your first local repo#
1
Navigate to the location in the filesystem where you want to keep your work. This could be the Desktop folder, for example, or some other location you choose.
cd ~/Desktop
Then create a new folder to serve as your local git repo and navigate into it.
mkdir my_local_repo && cd my_local_repo
If you followed these navigation steps exactly then you should now find yourself in a folder called my_local_repo
which is located in your Desktop folder. You can verify this by running
pwd
and you should see the following output.
/Users/<user>/Desktop/my_local
2
Your new folder is not yet a git repo. You can verify this by trying to run a git operation.
Run
git status
and you should receive the following error message.
fatal: not a git repository (or any of the parent directories): .git
You can verify this further by inspecting the current folder: running command ls
produces no output since it is empty. (Command ls
“lists” the contents of the folder.)
Run the command git init
to transform this folder into a git repo. This command initializes a new git repository in the current folder and option -b
for “branch” names the default branch.
Run
git init -b main
and you should see the following message.
Initialized empty Git repository in /Users/<user>/Desktop/my_local_repo/.git/
Now, git operations should work as intended and you’ll notice that the current folder is no longer empty.
Run
ls -A
and you should see the following output.
.git
3
Taking a peek inside folder .git
reveals a lot of stuff which you don’t need to worry about yet.
tree .git
You should see the following output.
.git
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
9 directories, 17 files
logs
COMMIT_EDITMSG
index
packed-refs
Setting up an SSH key for authentication#
This instruction set is based on the following documents:
1
Generate a new public-private key pair. Make sure to replace email@example.com
with your own comment.
ssh-keygen -t ed25519 \
-f ~/.ssh/id_ed25519_github_personal \
-C "<email@example.com>"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_github_personal -C "email@example.com"
2
You will be prompted to type a passphrase for your SSH key. Give your SSH key a strong passphrase and make sure to store it somewhere safe (e.g., with a password manager).
Type your passphrase once and then press Enter.
Type your passphrase again and then press Enter again.
3
Start the SSH agent in the background.
eval "$(ssh-agent -s)"
4
Add the SSH key to the SSH agent.
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_github_personal
ssh-add ~/.ssh/id_ed25519_github_personal
5
Setup the SSH config file.
Compatible shells:
Bash
Git Bash
Zsh
other Unix
echo '
Host github.com
AddKeysToAgent yes
Hostname github.com
IdentityFile ~/.ssh/id_ed25519_github_personal
Port 22
UseKeychain yes
User git
' >> ~/.ssh/config
Compatible shells:
Bash
Git Bash
Zsh
other Unix
echo '
Host github.com
AddKeysToAgent yes
Hostname github.com
IdentityFile ~/.ssh/id_ed25519_github_personal
Port 22
User git
' >> ~/.ssh/config
echo
for zsh echo -e
for bash
echo -e '\nHost github.com\n AddKeysToAgent yes\n Hostname github.com\n IdentityFile ~/.ssh/id_ed25519_github_personal\n Port 22\n UseKeychain yes\n User git' >> ~/.ssh/config
echo
for zsh echo -e
for bash
echo -e '\nHost github.com\n AddKeysToAgent yes\n Hostname github.com\n IdentityFile ~/.ssh/id_ed25519_github_personal\n Port 22\n User git' >> ~/.ssh/config
Then run
cat ~/.ssh/config
and verify that the contents of file ~/.ssh/config
contain the following text.
Host github.com
AddKeysToAgent yes
Hostname github.com
IdentityFile ~/.ssh/id_ed25519_github_personal
Port 22
UseKeychain yes
User git
Host github.com
AddKeysToAgent yes
Hostname github.com
IdentityFile ~/.ssh/id_ed25519_github_personal
Port 22
User git
6
Copy the public key to the clipboard.
tr -d '\n' < ~/.ssh/id_ed25519_github_personal.pub | pbcopy
cat ~/.ssh/id_ed25519_github_personal.pub | clip
7
In the web browser, navigate to your GitHub account.
Select “Settings” > “SSH and GPG Keys” and then click the green button “New SSH key” and paste your public key in the field “Key”.
Give your key a “Title” which refers to the particular machine holding the associated private key (e.g., Dave’s MacBook Pro).
If you would like to sign your commits, you may repeat this process a second time with the same “Key” and “Title” but witg the “Key type” set to Signing Key
rather than Authentication Key
. Make sure to check the box under “Vigilant Mode” and see the end of this section to finish setting up your signing key.
Open your web browser > navigate to gitlab > your account > Preferences > SSH keys > New SSH key
Give your key a “Title” which refers to the particular machine holding the associated private key (e.g., Dave’s MacBook Pro).
8
Once the public key has been added to your GitHub account, test the remote connectio from the terminal.
ssh -T git@github.com
ssh -T git@gitlab.com
If the remote connection is established, you may see a confirmation message similar to the following.
> Hi username! You've successfully authenticated, but GitHub does not
> provide shell access.
Welcome to GitLab, username!
Setting up an SSH key for signing#
This instruction set is based on the following documents:
In the terminal, run
git config --global gpg.format ssh &&
git config --global user.signingkey ~/.ssh/id_ed25519_github_personal.pub &&
echo "$(git config --get user.email) namespaces=\"git\" $(cat ~/.ssh/id_ed25519_github_personal.pub)" >> ~/.ssh/allowed_signers &&
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers &&
git config --global commit.gpgsign true &&
cat ~/.gitconfig
Verify that the contents of file ~/.gitconfig
contain the following text.
[commit]
gpgsign = true
[gpg]
format = ssh
[gpg "ssh"]
allowedSignersFile = ~/.ssh/allowed_signers
[user]
signingkey = ~/.ssh/id_ed25519_github_personal.pub
Managing remotes#
Add a remote named origin
with the URL git@github.com:USER/REPO.git
where USER
and REPO
are your own GitHub username and GitHub repo name, respectively.
git remote add origin git@github.com:USER/REPO.git
Change the remote origin
’s SSH URL to git@github.com:USER/REPO.git
, where USER
and REPO
are your own GitHub username and GitHub repo name, respectively.
git remote set-url origin git@github.com:USER/REPO.git
Remove references to remote origin
from local.
git remote rm origin
Get the remote’s URL.
git remote -v
Connect a local to a pre-existing remote#
1
cd .../local
2
git init
3
echo "# My New Repo" >> README.md
4
git add README.md
5
git commit -m "<message>"
6
git branch -M main
7
git remote add origin git@github.com:<github-username>/<github-repo-name>.git
8
git push -u origin main
Pushing changes from a local to a remote#
1
git status -bs
2
git add file1 file2
3
git status -bs
4
git commit -m "my commit message"
5
git status -bs
6
git push [-u origin <branch>]
7
git status -bs
The working directory and the staging area#
Stage a file (tracked or untracked).
git add <file>
Stage a tracked file for deletion from the repo (and from the working directory).
git rm <file>
Undo changes to a tracked but unstaged file in the working directory.
git restore <file>
Unstage a file while keeping its modifications in the working directory.
git restore -S <file>
Unstage a file and discard its modifications from the working directory.
git restore -SW <file>
Commit all files in the staging area.
git commit -m "<message>"
Commit an unstaged, tracked file.
git commit <file> -m "<message>"
Commit all unstaged, tracked files.
git commit -am "<message>"
Replace the previous commit with the current one.
git commit --amend
Working with GitHub’s CLI#
gh auth login
gh ssh-key list
gh config set git_protocol ssh &&
gh config set editor "code -w" &&
gh config set prompt enabled &&
gh config set pager cat &&
gh config list
gh repo list
.gitignore#
You can have a .gitignore
file in as many directories in your repo as you would like. A .gitignore
file affects its directory and all subdirectories. The pattern matching rules cascade: rules in higher directories can be overriden by inverted rules in subdirectories.
Precedence (from highest to lowest)
command line
.gitignore
file in the same directory.gitignore
files in parent directories, proceeding upward.git/info/exclude
filethe file specified by the configuration variable
core.excludesFile
You should place entries into your version-controlled .gitignore
files only if the patterns apply to all derived repos universally. If the exclusion pattern is specific to your one repo and is not applicable to anyone else’s clone of your repo then the patterns should go into the .git/info/exclude
file since that file is not propagated during clone ops.
Rules
blank lines are ignored
lines beginning with a pound sign
#
are commentsa literal file name matches a file in any directory with that name
a directory name is marked by a trailing slash
/
and matches the named directory and any subdirectory but does not match a file or a symbolic linka pattern containing shell globbing characters is expanded as a shell glob pattern; the match does not extend across directories
an initial exclamation point inverts the sense of the pattern on the rest of the line; any file excluded by an earlier pattern but matching an inversion rule is included; an inverted pattern overrides lower precedence rules
examples
*
matches zero or more charactersLinux: matches everything except slashes
Windows: matches everything except slashes and backslashes
**
recursively match zero or more directories under the current directory**/*.md
recursively matches all markdown files
?
matches a single character[ab]
eithera
orb
[A-Za-z0-9]
matches an alphanumeric character\
escapes a special symboltmp/
tmp/files.log
tmp/subdir/files.log
parent/tmp/files.log
grandparent/parent/tmp/files.log
file.log
file.log
dir/file.log
dir/**/file
dir/file
dir/subdir/file
dir/subdir/subsubdir/file
**/file
dir/file
anotherdir/file
file
file?.log
file1.log
file2.log
!important.log
(not ignored)important.log
dir/important.log
\#*.tmp
matches a file name beginning with a pound sign and ending with ‘.tmp’
.gitkeep#
Git keeps track of files, not folders. A folder will only be committed if it contains a file.
Managing branches#
Create a local branch and switch to it.
git switch -c <branch>
Switch to a branch.
git switch <branch>
Delete a local branch.
git branch -d <branch>
Delete a remote branch (and the associated remote-tracking branch).
git push -d origin <branch>
Managing large files#
git lfs version
One-and-done configuration.
git lfs install
Per repo.
git lfs track "*.pad"
git add .gitattributes
Preexisting files?: git lfs migrate
(examples)
git lfs migrate info
git lfs ls-files
git lfs clone --depth=1 <url>
Taking a peek inside the .gitattributes
file.
Large files with Git: LFS and git-annex
Careful
GitHub warns about files approaching
25 MB via browser
50 MB via command line
Files approaching 100 MB are blocked.
With Git LFS, you can store files up to 2 GB.
Be careful about which assets you deploy via Git LFS and when: monthly storage and bandwidth quotas.
Also, repos should be less than
1 GB (ideally)
5 GB (recommended)
Removing a large file from a repo’s history
Setting up a toy local-remote-collaborator system#
mkdir -p demo/{remote,local,collaborator} &&
cd demo/remote &&
git init &&
echo "# Demo" > README.md &&
git add README.md &&
git commit -m "initial commit" &&
cd ../local &&
git init &&
git remote add origin ../remote &&
git pull origin main &&
cd ../collaborator &&
git init &&
git remote add origin ../remote &&
git pull origin main &&
cd ..
or
mkdir -p demo/remote &&
cd demo/remote &&
git init &&
echo "# Demo" > README.md &&
git add README.md &&
git commit -m "initial commit" &&
cd .. &&
git clone remote local &&
git clone remote collaborator
brew install watch
watch -n1 -d find .
A view of the index and object store#
git init -b main && tree .git
.git
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
9 directories, 17 files
logs
COMMIT_EDITMSG
index
packed-refs
Hosting a web page with GitHub Pages#
To do
Commands#
add#
“make the copy in the index match the copy in the working directory”
copy from the working directory to the index if the working directory copy is edited
remove from the index if the working directory copy is removed
common idioms
git add <file>
stage a filegit add -A
orgit add .
stage all filesgit add -n
dry-rungit add -v
verbosegit add -p
orgit add --patch
patch modegit add -f
force add (override .gitignore)
git add [ -i | --interactive ]
am#
git am
“apply mailbox”
git am feature/0001-some-name.patch
apply a single patchgit am feature/*.patch
apply all patches in a directory
annotate#
git annotate <file>
similar to blame, different output format
apply#
git apply path/to/output.diff
apply a diff patch file
bisect#
git bisect start
starts bisect sessiongit bisect good <treeish>
orgit bisect old <treeish>
treeish can be branch, sha, tag, HEADgit bisect bad <treeish>
orgit bisect new <treeish>
git bisect reset
reset working directory to state prior to bisect session start
blame#
git blame filename.txt
annotate file with commit detailsgit blame -w filename.txt
annotate file with commit details (ignore whitespace changes)git blame -L 100,150 filename.txt
annotate lines 100-150git blame -L 100,+50 filename.txt
annotate lines 100-150git blame <sha> filename.txt
annotate file at revisiongit blame <sha> -- filename.txt
annotate file at revision
branch#
git branch
list local branchesgit branch -r
list remote-tracking branchesgit branch -a
list both local and remote-tracking branchesgit branch -v
list branches (verbose)git branch <branch>
create a new branchgit branch -M main
Rename the current branch tomain
.git branch --merged [ HEAD | local | origin/remote | 1234567 ]
list branches that have been merged into the current branchuses the current branch by default
branches whose tips are reachable from the specified commit (HEAD if not specified)
the branch tip is in the history of the specified commit
git branch --no-merged
list branches that have not been merged into the current branchgit branch -r --merged
list branches that have been merged into the current branch (remote)git branch -r --no-merged
list branches that have not been merged into the current branch (remote)git branch -d <branch>
Delete a local branch.git branch -D <branch>
Delete a local branch regardless of push or merge statusgit branch --set-upstream-to=origin/main main
git branch --move oldname newname
rename branch
cat-file#
git cat-file -p <hash prefix>
- show the contents of an object filegit cat-file -t <hash prefix>
- show the type of an object file
checkout#
git checkout
discards working directory (unstaged) changes, reverts to most recent commit stategit checkout <branch>
switch to an existing branchgit checkout -b <branch>
create a new branch and switch to itgit checkout <checksum>
detached stategit checkout -- <file>
remove file from staging area and discard working directory changes too. DEPRECATED! to discard changes to filein the working tree, see git restore <file>
git checkout -b <branch> <hash>
recover a branch after accidental deletion (use hash of the branch given by reflog)
cherry-pick#
git cherry-pick [ <sha> | <sha..sha> ]
git cherry-pick -e
orgit cherry-pick --edit
edit the commit message (otherwise the previous commit message is used)git cherry-pick --continue
git cherry-pick --skip
git cherry-pick --abort
clean#
git clean -f
remove untracked files from the working directory (doesn’t remove files that are ignored by git or files that are staged or committed)git clean -f -d
removes directories toogit clean -f -d -n
orgit clean -f -d --dry-run
clone#
git clone
create a complete copy of a repo
git clone <url> [<destination>]
commit#
git commit
add staged changes to the local repo
common idioms
git commit -m '<message>'
git commit -m '<message>' <file>
combines the two stepsgit add
andgit commit
for an existing tracked file (does not apply torm
ormv
)git commit -am '<message>'
git commit -am '<message>' --amend
modify the most recent commit (that hasn’t already been pushed)git commit -vv
verbosegit commit -p
patch mode
git commit [ -a | --all ]
git recursively traverses the entire repo; stages all known file changes, including removals of tracked files from the working directory; and commits them
more options
git commit [ --dry-run ] [ --interactive ]
low-level equivalent to git commit
git write-tree && echo -n '<message>' | git commit-tree <name>
commit-tree#
git commit-tree <hash prefix>
config#
git config -e
git config -l --show-scope --show-origin [ --global | --local | --system ]
more
git config --global fetch.prune true
(CAREFUL! this is a destructive default that may discard remote-tracking branches that you are still using locally)git config --global alias.praise blame
git config --global user.name <name>
git config --global user.email <email>
git config --global init.defaultBranch main
git config --global core.autocrlf [input (macOS) | true (Windows)]
git config --global core.editor [code --wait | emacs | nano | vim]
git config --global pager.branch false
git config --global pull.rebase ["false" (default: fast-forward if possible else merge commit) | "true" (rebase when pulling)]
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global commit.gpgsign true
git config --global gpg.format ssh
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers
git config --global gpg.ssh.program /Applications/1Password.app/Contents/MacOS/op-ssh-sign
git config --global user.signingkey ssh-ed25519 AAAA...
git config --global core.editor "'notepad++.exe' -multiInst -notabbar -nosession -noPlugins"
git config --global color.ui true
diff#
diff -r repo1 repo2
compare the contents of two repos (e.g., a repo and its clone)
git diff
compare changes in the working directory to those in the index
git diff [ --cached | --staged ]
compare changes in the index to those in the previous commit
git diff <from-commit> <to-commit> > output.diff
create a diff patch
diff-tree#
git diff-tree
compare the differences between two tree objects (shows the changes made to the file structure and contents)
git diff-tree <commit1> <commit2>
difftool#
git difftool --tool-help
fetch#
git fetch
changes from the remote repo are copied to the remote-tracking branch
git fetch
fetches commits and tags from the default remote (origin)git fetch --all
fetches commits and tags from all remotesgit fetch --tags
fetches tags only (with necessary commits only)git fetch -p
orgit fetch --prune
shortcut: prune, then fetch
format-patch#
git format-patch -1 <sha>
export a single commitgit format-patch main
orgit format-patch main..HEAD
export all commits on the current branch that are not in the main branchgit format-patch <sha>..<sha>
export all commits in the rangegit format-patch <sha>..<sha> -o ./feature_patches
put patch files into a directorygit format-patch <sha>..<sha> --stdout > feature_patch
output patches as a single file
gc#
git gc
“garbage collection”
hash-object#
git hash-object <name>
echo 'hello world' | git hash-object --stdin
help#
git help -a
list command categories
init#
git init
setup a local repogit init --bare
log#
git log
view the commit history
git log
orgit log HEAD
view the commit history reachable from HEADgit log filename.txt
lists commits that changed filename.txt (this is a log of this file’s activity in the current branch)git log <branch>
show commit history below branchgit log --all
show commit history below all branchesgit log --graph
git log --oneline
orgit log --pretty=oneline
seven-digit ID + commit messagegit log --oneline --graph --all [--decorate]
useful for visualizing branchesgit log -p
orgit log --patch
list commits as patches (diffs)git log -2
git log -p -2
git log --stat
git log --pretty=short
git log --pretty=full
git log --pretty=fuller
git log --pretty=format:"%h - %an, %ar : %s"
git log --pretty=format:"%h %s" --graph
git log -L 100,150:filename.txt
list changes to lines 100-150 in filename.txtgit log -L 100,+50:filename.txt
list changes to lines 100-150 in filename.txtgit log --since=2019-05-14
git log --author=Dave
git log --grep='<regex>'
search commit messagesgit log --grep='nothing' --author=Dave --since=2019-05-04
git log --grep='nothing' --grep='commit' --all-match
logical andgit log --grep='nothing' --grep='commit' --all-match --invert-grep
negation
git log
operates on a series of commits
ls-files#
git ls-files
- show the contents of the index
git ls-files [ -s | --stage ]
show staged contents’ mode bits, object name, and stage number in the outputgit ls-files --debug
ls-tree#
git ls-tree --full-tree --name-only -r HEAD
List currently-tracked files.
merge#
git merge <branch>
git merge --squash
merge-base#
git merge-base main new_feature
return commit where topic or feature branch diverges from the source branch (shows the commit that is the current base)
mergetool#
mv#
git mv
stage a file for renaming in the repo
git mv
is equivalent to
mv foo.html bar.html
git rm foo.html
git add bar.html
prune#
git prune
prune all unreachable objects (part of git’s automatic garbage collection)
pull#
git pull
fetch the latest commits from a remote repo and merge them into the current branch; affects only the current branch by default; fetched commits are stored in the associated remote-tracking branch; untracked changes are overwritten (your local repo should be stashed, committed, and free of untracked changes before pulling)git pull -v
verbosegit pull -r
orgit pull --rebase
rebase instead of mergegit pull --rebase=preserve
preserve locally-created merge commits (use if commits that you are rebasing include merge commits)git pull --rebase=interactive
git pull --allow-unrelated-histories
push#
git push
changes in the local repo are pushed to the remote repo
git push
push current branchgit push origin <branch>
push branchgit push --all
push all branchesgit push -u origin main
orgit push --set-upstream origin main
create a branch in the remote repo called maingit push -v
verbosegit push -f
orgit push --force
force push (CAREFUL! this rewrites the history)git push -d origin <branch>
orgit push --delete origin <branch>
Delete a remote branch (automatically deletes the remote-tracking branch).git push origin <tag>
push some taggit push origin --tags
push all tagsgit push origin --follow-tags
push annotated tags onlygit push -d origin <tag>
orgit push --delete origin <tag>
Delete a remote tag.git push origin :refs/tags/<tag>
delete a remote tag ???git push --force-with-lease
allows the push to proceed only if the remote branch’s expected state matches the local branch’s expected state, based on the previously known state of the remote branch; a safer alternative to git push -f as it helps prevent inadvertently overwriting commits that you are not aware of
rebase#
git rebase main
rebase current branch to tip of maingit rebase main new_feature
rebase new_feature branch to tip of maingit rebase --onto newbase upstream branch
rewind branch to point of merge with upstream then replay commits in order onto tip of newbasegit rebase --continue
git rebase --skip
git rebase --abort
git rebase -i main new_feature
interactive modegit rebase -i HEAD~3
rebase the last three commits onto the same branch, with the interactive opportunity to modify them (CAREFUL! destructive; useful for local commits that haven’t been shared with others)
reflog#
git reflog
reference logs store updates to branch tips and other references in the local repo
remote#
git remote
list remotesgit remote -v
list remotes (verbose)git remote show <url>
git remote add origin <url>
git remote get-url origin [--all]
git remote rename origin destination
git remote rm origin
git remote set-url origin <url>
git remote prune origin [--dry-run]
delete stale remote-tracking branches
reset#
git reset
move the current branch pointer to a specific commit/position
git reset --soft HEAD~1
undo previous commit and put changes back in the staging area (working directory changes are left alone)git reset --hard HEAD~1
undo previous commit and discard commit changes (working directory changes are discarded and working directory reflects the state of the previous commit) (CAREFULE! destructive)git reset --hard origin/main
force an overwrite of your local files by the state of remote main branchgit reset HEAD <FILE>
[OLD] to unstage; seegit restore -S <FILE>
git reset HEAD@{1}
git reset HEAD^
orgit reset HEAD^1
the commit’s first parentgit reset HEAD^2
the commit’s second parentgit reset HEAD~
orgit reset HEAD~1
the commit’s first parentgit reset HEAD~2
orgit commit HEAD^^
the commit’s first parent’s first parentgit reset HEAD~n
the commit’s nth parent (n commits before HEAD, the nth generation ancestor of HEAD)git reset -p
patch modegit reset <file>
move staged file out of staging area (preserve working directory changes)
restore#
git restore <file>
orgit restore -W <file>
orgit restore --worktree <file>
undo changes to a file in the working directorygit restore .
orgit restore -W .
orgit restore --worktree .
undo changes to all file in the working directorygit restore -S <file>
orgit restore --staged <file>
unstage a filegit restore -S .
orgit restore --staged .
unstage all filesgit restore -p
patch mode
rev-list#
rev-parse#
git rev-parse <commit> [ --is-bare-repository ]
translates a relative commit name to the absolute commit name
revert#
git revert <checksum-id>
(does not rewrite the history, but adds a new commit to the history)
rm#
git rm
stage a file for removal from the repo
git rm
is the inverse of git add
: it removes a file from both the repo and the working directory
git rm --cached <file>
remove a file from the index only (the copy of the file in the working directory is left alone)
“Using git rm --cached
to make a file untracked while leaving a copy in the working directory is dangerous because you may forget that it is no longer being tracked. Using this approach also overrides Git’s check that the working file’s contents are current.”
rm <file>
remove a file from the working directory only
git rm <file>
remove a file from both the index and the working directory (“Before Git removes a file, it checks to make sure the version of the file in the working directory matches the latest version in the current branch. Use git rm -f
to force the removal of the file even if you have altered it since your last commit.”)
git restore --staged <file>
copies the file from commit HEAD into the index so that the index copy and the HEAD copy now match (the copy of the file in the working directory is left alone)
if HEAD lacks the file, this has the effect of removing the file from the index
git reset <file>
copies the file from ommit HEAD into the indext
git restore -SW <file>
is equivalent to git checkout HEAD -- <file>
show#
git show
orgit show HEAD
show changes of the previous commitgit show [ <checksum> | <sha> | <tag> ]
git show <checksum> -2
git show <checksum> --show-signature
git show refs/head/<branch>
explicit path to branch; useful if branch name matches tag name for examplegit show <commit> --stat
display a histogram showing inserts, deletion, and modifications per file for a specific commit along with its general commit information
show-branch#
view concise, one-line summaries for the current branch
show-ref#
git show-ref --head
finds the HEAD of the current branch
stash#
git stash
git stash save <name>
git stash pop
applies the topmost entry in the stash to the working files and removes it from the stashgit stash apply
applies the topmost entry in the stash to the working files but leaves it in the stashgit stash -p
patch modegit stash branch <branch>
restore some previously stashed work to a new branchgit stash show -p stash@{2}
see the details of the changes in the first of three stash entries
status#
git status
- query the state of the index (compares the virtual tree state with the working tree and displays the difference as the output)
git status
git status -bs
git status -u
git status -v
git status -vv
submodule#
switch#
git switch <branch>
switch to branchgit switch -c <branch>
create a new branch and switch to itgit switch -
return to the previously checked out branch
symbolic-ref#
tag#
git tag
orgit tag -l
orgit tag --list
list all tags (tags are not tied to branches; they just point to commits regardless of whcih branches they’re on)git tag -l "v2*"
list tags beginning with “v2”git tag -n
(implies-l
) list tags with the first line of each annotation (or commit message if no annotation)git tag -n5
(implies-l
) list tags with the first five lines of each annotation (or commit message if no annotation)git tag -s <tag> [<commit>]
lightweight taggit tag -s -a <tag> [<commit>] -m 'Version 0.1'
annotated taggit tag -d <tag> [<commit>]
orgit tag --delete <tag> [<commit>]
delete a taggit tag -v <tag> [<commit>]
verify signed tag
write-tree#
git write-tree
create a tree object from the current index
worktree#
Resources#
[ h ] Graphite
More Git Providers
[2020][2019] MIT Missing Semester: Version Control
David Mahler
[ y ] David Mahler. (2018). “Introduction to Git - Remotes”.
[ y ] David Mahler. (2017). “Introduction to Git - Branching and Merging”.
[ y ] David Mahler. (2017). “Introduction to Git - Core Concepts”.
freeCodeCamp
[ y ]
11-18-2021
. freeCodeCamp. “Advanced Git Tutorial - Interactive Rebase, Cherry-Picking, Reflog, Submodules, and more”. YouTube.[ y ]
09-30-2021
. freeCodeCamp. “Git for Professionals Tutorial - Tools & Concepts for Mastering Version Control with Git”. YouTube.[ y ]
07-13-2021
. freeCodeCamp. “Git Branch Tutorial”.[ y ]
11-24-2020
. freeCodeCamp. “How to Undo Mistakes WIth Git Using the Command Line”. YouTube.[ y ]
05-28-2020
. freeCodeCamp. “Git and GitHub for Beginners - Crash Course”. YouTube.[ y ]
09-23-2016
. freeCodeCamp. “Git & GitHub”.
YouTube
[ y ] GitLab. (14 Nov 2017). “Git Internals - How Git Works - Fear Not The SHA!”. YouTube.
[ y ] Mr. P Solver. (16 May 2022). “A Git Tutorial Based On Examples (Code Along!)”. YouTube.
[ y ] CS50. (11 Apr 2018). “Git Internals by John Britton of GitHub - CS50 Tech Talk”. YouTube.
[ y ] HackersOnBoard. (2013). “[Linux.conf.au 2013] - Git For Ages 4 and Up”.
VS Code
[ d ] Using Git source control in VS Code
[ e ] Git Graph extension
[ e ] Git History extension
[ e ] GitLens extension
More
[ y ]
02-08-2024
. GitButler. “So You Think You Know Git - FOSDEM 2024”.
Texts#
Chacon, Scott & Ben Straub. (2014). Pro Git: Everything You Need to Know About Git, 2nd Ed. Apress. Home.
Ponuthorai, Prem Kumar & Jon Loeliger. (2022). Version Control with Git: Powerful Tools and Techniques for Collaborative Software Development. 3rd Ed. O’Reilly.
Silverman, Richard E. (2013). Git Pocket Guide: A Working Introduction. O’Reilly.
Vinto, Natale & Alex Soto Bueno. (2023). GitOps Cookbook: Kubernetes Automation in Practice. O’Reilly.
Terms#
[ w ] Atlassian
[ w ] Atomic Commit
[ w ] Binary Large Object (BLOB)
[ w ] Bitbucket
[ w ] Branch
[ w ] Centralized Version Control
[ w ] Changeset
[ w ] Checksum
[ w ] Commit (version control)
[ w ] Commit (data management)
[ w ] Concurrent Versions System (CVS)
[ w ] Content-Addressable Storage (CAS)
[ w ] Darcs
[ w ] Data Differencing
[ w ] Delta Encoding
[ w ] Detached State
[ w ] diff
[ w ] diff3
[ w ] Directory Structure
[ w ] Distributed Version Control
[ w ] Edit Conflict
[ w ] etckeeper
[ w ] Forge
[ w ] Git
[ w ] Git Provider
[ w ] git-annex
[ w ] GitHub
[ w ] GitLab
[ w ] GNU Arch
[ w ] GNU Bazaar
[ w ] Local Folder
[ w ] Local Repository
[ w ] Mercurial
[ w ] Merge
[ w ] Merge Conflict
[ w ] Merkle Tree
[ w ] Metadata
[ w ] Microsoft Visual SourceSafe (VSS)
[ w ] Monorepo
[ w ] Monotone
[ w ] Patch
[ w ] patch
[ w ] Pull Request
[ w ] Remote Repository
[ w ] Repository
[ w ] Revision Control System (RCS)
[ w ] Revision Tag
[ w ] Source Code Control System (SCCS)
[ w ] Source Code Management (SCM)
[ w ] Staging Area
[ w ] Subversion (SVN)
[ w ] Tag
[ w ] Three-Phase Commit Protocol
[ w ] Tree
[ w ] Tree Structure
[ w ] Two-Phase Commit Protocol
[ w ] Version Control
[ w ] Working Tree
Notes#
\(1\quad\) Working Directory
files are edited in the working directory
git add; git rm; git mv
“staging a file”; “caching a file”; “putting a file in the index”
a file is copied into the object store and indexed by its SHA-1
\(2\quad\) Index (or Cache or Staging Directory) (virtual, mutable data structure)
the index collects (or “stages”) alterations to files as a final step before commit (i.e., it tracks what you want to commit)
the index caches info about the working directory, and the next revision to be committed
the index serves as an interface between the working tree and the object database
the index is a binary file and so stores binary data
its content is transient; and describes the structure of the entire repo at a specific point in time
a virtual tree state representing a tree object that will be referenced by a future commit
a sorted list of file pathnames in its virtual state along with its permissions and a reference to the SHA-1 ID of the corresponding blob objects
a cached representation of all the blob objects that reflect the current state of the working directory
the index does not contain file content
plays a role in the following: committing; querying the status of the repo; merging branches
git commit
checks the index–not the working directory–to discover what to commit as a single changeset
\(3\quad\) Repo (or Local Commit History) (virtual)
the repo is a key-value pair database
Object Store (or Object Database) (append-only data structure) - contains the original data files; log messages; author info; datetimes; etc.
Blobs “Binary Large Object” (immutable) - represents a version of a file; contains the file’s data only
a version of a file that holds the file’s data; a blob’s name is a hash of its content
Trees (immutable) - represents one level of directory info (blob IDs; pathnames; metadata; recursively references other subtree objects); a snapshot of the source tree that contains a list of file names each with a reference to a blob or tree object
Commits (immutable) - points to a tree object; links tree objects together into a history; contains the name of the tree object, a timestamp, a log message, and the names of zero or more parent commit objects
Author
Committer
Commit Date
Log Message
Tags - assigns a human-readable name to an object; a container that contains a reference to another object with metadata
Annotated Tag (immutable) - creates an object in the object store
Lightweight Tag (mutable) - a reference to a commit object; private to the repo; not stored in the object store
Packfile?
Git file classification
Tracked - a tracked file is a file that is either in the repo or in the index
Ignored - an ignored file must be explicitly declared as such even though it may remain present in the working directory
Untracked - an untracked file is a file that is neither tracked or ignored
working directory - (tracked + ignored) = untracked
A commit holds a snapshot.
A snapshot is made from the files in the index.
Hashes#
print(10**48 < 2**160 < 10**49)
print(10**48)
print(2**160)
print(10**49)
print(int(1e12) * int(1e12) * 60 * 60 * 24 * 365 * int(1e12))
# a trillion people each of whom produces
# a trillion new unique blobs per second for
# a trillion years
# is still five orders of magnitude less
True
1000000000000000000000000000000000000000000000000
1461501637330902918203684832716283019655932542976
10000000000000000000000000000000000000000000000000
31536000000000000000000000000000000000000000
Commits#
Git stores a commit object that contains
a pointer to the snapshot of the content that was staged
the author’s name and email
the commit message
pointer(s) to the parent commit
0 for initial commit
1 for normal commit
2 or more for merge commit
Commits
a commit represents a single atomic changeset w.r.t. the previous state: regardless of the number of directories, files, lines, or bytes that change with a commit, either all changes apply or none do.
a commit snapshot represents the state of the total set of modified files and directories–which means it represents a given tree state
a changeset between two snapshots represents a complete transformation from one tree state to another
Changeset
Git also records a mode flag indicating the executability of each file; changes to this flag are also part of the changeset
Absolute Commit Name - references a commit explicitly
the globally unique 40-digit hexadecimal SHA-1 commit object ID - globally unique not just for one repo but for any and all repos
Relative Commit Name (ref, symref) - references a commit implicitly
a ref points to a SHA-1 within the Git object store (it usually refers to a commit object)
a symbolic reference is a name that indirectly points to a Git object
each symref has a full name that begins with
refs/
; there are three namespaces.git/refs/heads/
(local branch names)refs/heads/feature
.git/refs/remotes/
(remote tracking branch names)refs/remotes/origin/main
.git/refs/tags
(tag names)
internal
.git/HEAD
a pointer to the most recent commit in the currently checked-out branch.git/ORIG_HEAD
.git/FETCH_HEAD
.git/MERGE_HEAD
.git/CHERRY_PICK_HEAD
ref^n
is the n-th parent of commitref
ref^
is the same asref^1
ref^^
is the same asref~2
and is the first parent of the first parent of commitref
ref~n
is the first [ parent, grandparent, great-grandparent, etc. ] of commitref
ref~
is the same asref~1
^ref
exclude commitref
set difference
X..Y
the set of commits starting afterX
up to and includingY
; or^X Y
all commits reachable fromY
minus all commits up to and includingX
..Y
is equivalent toHEAD..Y
X..
is equivalent toX..HEAD
set symmetric difference (the set of commits that are reachable from either or but not both)
X...Y = X Y --not $(git merge-base --all X Y)
“show everything in branch
X
or in branchY
but only back to the point where the two branches diverged”
Example
b..e = ^b e
= e - b = {a, b, c, d, e} - {a, b}
= c d e
main
/
a <- b <- c <- d <- e
- - -
Example - main at v is merged into feature at b
feature..main = ^feature main
= main - feature = {t, u, v, w, x, y, z} - {a, b, c, d, e, t, u, v}
= w x y z
feature
/
a <- b <- c <- d <- e
/ main
/ /
t <- u <- v <- w <- x <- y <- z
- - - -
Example - feature is merged into main
feature..main = ^feature main
= main - feature = {a, b, v, w, x, y, z} - {a, b}
= v w x y z
feature
/
<- a <- b
\ main
\ /
<- v <- w <- x <- y <- z
- - - - -
Example - feature at d is merged into main at z
feature..main = ^feature main
= main - feature = {a, b, c, d, t, u, v, w, x, y, z} - {a, b, c, d, e, t, u, v}
= w x y z
feature
/
a <- b <- c <- d <- e
/ \ main
/ \ /
t <- u <- v <- w <- x <- y <- z
- - - -
Example
dev...main
= dev main $(git merge-base --all dev main)
= dev OR main AND NOT (merge-base --all dev main)
= (dev OR main) - (dev AND main)
main = {a, b, c, d, e, f, g, h, i, u, v, w}
dev = {a, b, c, u, v, w, x, y, z}
= d e f g h i x y z
main
/
a <- b <- c <- d <- e <- f <- g <- h <- i
\ - - - /- - - dev
\ / /
u <- v <- w <- x <- y <- z
- - -
Branches#
Branch
a file that contains the 40-character SHA-1 checksum of the commit it points to
creating a branch is as quick and as simple as writing 41 bytes to a file (40 characters and a newline)
Three kinds of branches
branch on the remote repo (bugfix)
local snapshot of the remote branch (origin/bugfix)
local branch tracking the remote branch (bugfix)
Identify merged branches
list branches that have been merged into a branch
useful for knowing what commits / feature branches have been incorporated
useful for cleanup after merging many feature branches (makes sure nothing is deleted that hasn’t yet been merged in)
Prune (delete all) stale branches
stale branch: a remote-tracking branch that no longer tracks anything because the actual branch in the remote repo has been deleted
when you delete a remote branch, the associated remote-tracking branch is also deleted
when collaborators delete a remote branch, your remote-tracking branch remains: remote-tracking branches have to be manually pruned
git fetch does not automatically delete remote-tracking branches