Git
baddass version control
Links
- https://git-scm.com/book/
- https://docs.github.com/en/get-started/using-github/github-flow
- https://github.com/metacloud/gilt: gilt - A GIT layering tool
- https://github.com/git/git/tree/master/Documentation/RelNotes: Git release notes
- https://guides.github.com/introduction/flow/index.html: Understanding the GitHub flow
- http://nvie.com/posts/a-successful-git-branching-model: A successful Git branching model
- https://chris.beams.io/posts/git-commit: How to Write a Git Commit Message
- https://www.conventionalcommits.org: Conventional Commits: A specification for adding human and machine readable meaning to commit messages
- https://github.com/googleapis/release-please: Release Please automates CHANGELOG generation, the creation of GitHub releases, and version bumps for your projects.
- https://diziet.dreamwidth.org/14666.html: Never use git submodules
- https://forgejo.org: git hosting software with organization and user management in a web UI
- https://github.blog/2023-10-16-measuring-git-performance-with-opentelemetry
- https://glasskube.dev/guides/git/: "The guide to Git I never had."
- https://pre-commit.com: I use this in nearly every git repo I create, and I suggest everybody else do the same.
- https://stefaniemolin.com/tags/pre-commit%20hooks/: Stefanie Molin's pre-commit articles
- https://www.golinuxcloud.com/git-head-caret-vs-tilde-at-sign-examples: "Understanding git HEAD~ vs HEAD^ vs HEAD@{}"
- https://blog.izissise.net/posts/gitconfig
Examples
git init
Create a git repository for the CWD
git init
echo "" >> .gitignore
echo "# Ignore other unneeded files.
*.swp
*~
.DS_Store" >> .gitignore
git clone
Clone a local repo
git clone /path/to/repo
Clone a remote git repo via ssh
git clone user@ssh_server:/opt/git/project
Clone and specify a key when using ssh-agent
When using ssh-agent, if you have several keys loaded this can present problems. One of those problems is if you have multiple keys loaded that have different authorizations on a remote git server like Github. For instance, say you have ~/.ssh/id_ed25519_home
as your personal private key and ~/.ssh/id_ed225519_work
as your private work key. If you try to clone a work repo and git tries to authenticate with your home identity first, it will be unauthorized and the clone will fail, even though you have a second identity loaded that could have succeeded. To work around this, do something like:
export GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -i $HOME/.ssh/id_ed225519_work"
git clone git@github.com:your-work-gh-org/super-secret-repo.git
This works fine for one key having authorization to all private repos. This becomes difficult when you have multiple repos, each with a key that only has authorization for that single repo, such as using deploy keys tied to a single repo. If you try that when doing something like yarn install
, which is trying to clone multiple private repos, it will fail 100% of the time. In that case, you can follow the next example. You may have to read it twice, because setting up the configuration makes more sense if you do ssh then git, but logically it makes more sense if you go from git into ssh, which is how the process actually flows.
Construct an ssh_config file that configures a unique Host
for each private repo you need to clone and private key file that has access to it:
Host secret_repo_1
Hostname github.com
IdentityFile ~/.ssh/private_key_for_repo_1
Host secret_repo_2
Hostname github.com
IdentityFile ~/.ssh/private_key_for_repo_2
IdentitiesOnly yes
The ssh_configs above allow ssh access to what is essentially a host alias where the key that is authorized for the repo is used when ssh tries to connect to the correlated Host entry. In the next step, we do the same thing in reverse by crafting a gitconfig
file with one stanza for each of the ssh Host entries from your ssh_config, pointing it back to github:
[url "git@secret_repo_1:your-work-gh-org/super-secret-repo-1.git"]
insteadOf = git@github.com:your-work-gh-org/super-secret-repo-1.git
[url "git@secret_repo_2:your-work-gh-org/super-secret-repo-2.git"]
insteadOf = git@github.com:your-work-gh-org/super-secret-repo-2.git
We then export two variables in the shell:
export GIT_SSH_COMMAND="ssh -F $PWD/your_crafted_ssh_config_file"
export GIT_CONFIG_GLOBAL="$PWD/your_crafted_gitconfig_file"
What happens next is when you execute git clone git@github.com:your-work-gh-org/super-secret-repo-1.git
, which is the originally source git URL, your git_config alters the URL to be git@secret_repo_1:your-work-gh-org/super-secret-repo-1.git
. The server name is passed into ssh
, which uses your custom ssh_config file to connect to github.com using the options for that entry that are specified in the git_config, which includes using the identity file that is unique to that git repository. The same series of steps happens for secret_repo_2. The result is that each of these git repositories can be cloned using their original github URL, but these custom ssh configs are used in the process, which allows the right authentication mechanisms to be used for each individual git repository. This all happens without us having to alter the source code of the repo we are building, EG: without modifying package.json
which is used by yarn
. Using these techniques, we can set up CI to build software from private repositories using deploy keys where otherwise we would be unable to due to ssh authentication errors that might work fine for somebody who has a single ssh key that is authorized to clone all of the repositories.
git filesystem operations
Add everything in the CWD to the git repo
git add .
Rename a file in the git repo
This also renames the filesystem file.
git mv README.rdoc README.md
Delete a file from the repo
git rm filename
git status
Check the status of git
git status
git commit
Commit the current changes
git commit -m "Initial commit"
Commit all changes with commit -a
git commit -a -m "Improve the README file"
Skip git commit hooks
git commit --no-verify
git tag
https://git-scm.com/book/en/v2/Git-Basics-Tagging
Git supports two types of tags: lightweight and annotated.
Create an annotated tag
Annotated tags, are stored as full objects in the Git database.
git tag -m "Improve X and Y." v0.5.3
Create a light tag
This is basically DNS A records for git SHAs. The SHA is referenced by the tag, no other info is stored. Using them is generally frowned upon because tags tend to be used where context is important, so the annotations that go with an annotated tag are more suitable.
git tag v0.5.3
Delete a local tag
git tag -d v0.5.3
Delete a remote tag
git push --delete origin v0.5.3
Show what tags contain a given sha
git tag --contains abc123
git config
git config
interacts with configs. There are three scopes: --local, --global, --system.
- Local = per-repo settings. IE: stored in .git/config directory for the repo
- Global = per-user settings. IE: stored in ~/.gitconfig
- System = per-system settings, found in /etc/ or wherever git is looking for system settings.
Always use ssh for github.com
git config --global url."git@github.com:".insteadOf "https://github.com/"
Git client setup
This creates and modifies ~/.gitconfig with some parameters:
git config --global user.name "Daniel Hoherd"
git config --global user.email daniel.hoherd@gmail.com
git config --global alias.co checkout
git config --global core.editor "vim"
git config --global merge.tool vimdiff
git config --global log.date iso
Edit a .git/config file with some params
git config --replace-all svn-remote.svn.url https://svn.example.com/ops/
git config --replace-all svn-remote.svn.fetch ops:refs/remotes/trunk
git config --add svn-remote.svn.preserve-empty-dirs true
git config --unset svn-remote.svn.branches
git config --unset svn-remote.svn.tags
git config --add svn.authorsfile /srv-cluster/git-svn/git/author.txt
Show your configs in a dotted one-one-per-option format
git config --list
Also include the file that each configuration setting is defined in:
git config --list --show-origin
git diff
Show differences between objects and stuff.
diff between staged and committed
This is useful when you're adding files that were not previously in the repo alongside changes to existing files, since a bare git diff
before adding the files will only show changes to files that were already in the repo.
git diff --staged
diff that shows per-word colored differences
git diff --color-words
Machine readable word diff
git diff --word-diff
Diff and ignore whitespace
This does not ignore line ending changes or blank line insertion and removals.
git diff -w
Show diffs between master and a given date
git diff $(git rev-list -n1 --before="1 month ago" master)
Show what has changed since a point in time
git whatchanged --since="18 hours ago" -p
or...
git whatchanged --since="18 hours ago" --until="6 hours ago" -p
git blame
git blame
shows information about the commit associated with each line of a file.
Simple usage
git blame <filename>
Show non-whitespace changes in blame
When somebody has reformatted code but didn't make any code changes, this will show the prior commits where something more than whitespace changed.
git blame -w <filename>
git log
Shows commit history.
View the commit history
git log
Show one log entry
git log -1
Show git commits that contain a given string
This searches the content of the diff, not the commit message.
git log -S search_string
Show commit messages that match a given regex
git log --grep='[Ww]hitespace'
Show logs for a given dir in the last 3 days
git log --since=3.days modules/profile_sensu
Show raw log history for 5 most recent commits
Useful for seeing TZ settings.
git log --format=raw -5
Really pretty logs
log --graph --oneline --decorate --all
git shortlog
Show number of commits by user, including e-mail
Using the -e
flag includes e-mail address. The list is unique per entry, so if you use a different name along with the same e-mail address, that shows up as two entries in the list.
git shortlog -ens
Keep in mind this is commits, not lines within the current codebase. If the repo is old, this information may not be useful for finding people who are in-the-know about the current contents of the repo. This is useful for preparing a user list for a git filter-repo
operation.
git show
Show the changes from a specific SHA
git show f73f9ec7c07e
Show a complete file as of a given SHA
This is an absolute path from the git root, not relative to CWD. This command will show the whole file as of the given SHA.
git show f73f9ec7c07e:dir/filename.yaml
git branches
Branches are an integral part of git. They allow you to work on distinct changes without mixing them all up together.
Create a branch
git checkout -b readme-fix
Check which branch you're in
git branch
Rename (move) a branch
git branch -m oldname newname
Show what branches contain a given sha
git branch --contains abc123
git merge
This lets you merge two branches.
Merge branch with master
git checkout master
git merge readme-fix-branch
git branch -d readme-fix-branch
disable fast-forward merges
You can control how the history is kept when merging. By default, fast-forward merges occur, which replays the commits on the branch that is being merged into. By disabling this you can see several commits being merged from one branch into another, making it easier to roll back that whole series of commits without digging through the history to see where each commit from the branch came from.
git config --global merge.ff false
git filter-repo
git filter-repo
is not a standard tool with git, but can be installed separately. It is used for rewriting the history of the git repo, allowing move, rename, merging and trimming operations, rewriting commit IDs, and more. This is a destructive tool, so it should be performed on a fresh clone of the repo while you iterate on finding the right sequence of operations to perform to get to your desired state. The destructive nature is that it rewrites the entire history of the repo, so if your repo depends on specific SHAs or any other specific history, you probably need to take a harder look at how to solve your problem.
Extract one subdir into its own repo, renaming some files
This is great if you want to extract part of a repo for public release, or just for organizational purposes.
Extract the path scratch-work/scripts
into bin
, removing all other history in the repo.
git-filter-repo --path scratch-work/scripts --path-rename scratch-work/scripts:bin
remotes
Add a remote
git remote add upstream https://github.com/danielhoherd/homepass
Push to a specific remote
# push to the master branch of the remote named upstream
git push upstream master
Alter the source of origin
If you move your repo to another location, use this command to change the upstream URL:
git remote set-url origin https://user@newhost/newpath/reponame
git reset
git reset
allows you to reset your state to what it was at a previous point.
Reset to a prior state based on what has been done locally
The reflog is a log of what steps have been performed locally. You can view the reflog, then reset to a prior state.
git reflog # show all HEAD changes
git reset --hard 45e0ae5 # reset all git tracked state to 45e0ae5
Alternately, you can use a date:
git reflog --date=iso # absolute date based reflog references
git reset "HEAD@{2015-03-25 14:45:30 -0700}" --hard
Reset feature branch to state when it was branched from master
Do this if you want to start your branch over with only the current changes. This is useful if you've been iterating through lots of bad changes that were committed and want to clean them all out. It basically lets you squash to a single commit on your branch.
git reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))
Hard reset of local changes
This will abandon all local changes and resolve merge conflicts
git fetch origin
git reset --hard origin/master
git clean
Remove all untracked files and directories
This is useful after your reset to a prior state. It deletes all files and directories that show up in the untracked section of git status
git clean -ffdx
Miscellaneous tricks
Refresh all Git repos in a path
find /var/www/html/mediawiki/ -name .git | while read -r X ; do
pushd "$(dirname "${X}")" && \
[ $(git remote -v | wc -l) -gt 0 ] && \
git pull && \
popd ;
done ;
Show a numbered list of remote branches sorted by last commit date
git branch -r | grep -v HEAD | xargs -r -n1 git log -1 \
--pretty=format:'%ad %h%d %an | %s %n' --date=iso -1 | sort | nl -ba
Branch cleanup
git gc --prune=now
git remote prune origin
git grep
Find a string in all branches
This finds the word "hunter2" in the tests directory of all branches.
git grep '\bhunter2\b' $(git branch -a --format='%(refname:short)') tests/
Exclude certain directories from git grep
results
You can accomplish this using pathspec syntax.
git grep searchstring -- ':!excluded-dir' ':!*junk-glob*'
See the pathspec definition in the git glossary for more info.