Git¶
baddass version control
Links¶
- https://git-scm.com/book/
- https://docs.github.com/en/get-started/using-github/github-flow
- https://github.com/retr0h/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
- https://github.com/dandavison/delta: "A syntax-highlighting pager for git, diff, grep, and blame output"
Examples¶
git init¶
Create a git repository for the CWD¶
git init
echo "" >> .gitignore
echo "# Ignore other unneeded files.
*.swp
*~
.DS_Store" >> .gitignore
Debug your .gitignore¶
This will print out which rule is ignoring the given things, if any of them match any rules, and will print nothing if none of the given items are ignored.
git clone¶
Clone a local repo¶
Clone a remote git repo via ssh¶
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¶
Rename a file in the git repo¶
This also renames the filesystem file.
Delete a file from the repo¶
git status¶
Check the status of git¶
git commit¶
Commit the current changes¶
Commit all changes with commit -a¶
Skip git commit hooks¶
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.
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.
Delete a local tag¶
Delete a remote tag¶
Show what tags contain a given sha¶
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 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¶
Also include the file that each configuration setting is defined in:
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.
diff that shows per-word colored differences¶
Machine readable word diff¶
Diff and ignore whitespace¶
This does not ignore line ending changes or blank line insertion and removals.
Show diffs between master and a given date¶
Show what has changed since a point in time¶
or...
git blame¶
git blame shows information about the commit associated with each line of a file.
Simple usage¶
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 log¶
Shows commit history.
View the commit history¶
Show one log entry¶
Show git commits that contain a given string¶
This searches the content of the diff, not the commit message.
Show commit messages that match a given regex¶
Show logs for a given dir in the last 3 days¶
Show raw log history for 5 most recent commits¶
Useful for seeing TZ settings.
Really pretty logs¶
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.
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¶
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 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¶
Check which branch you're in¶
Rename (move) a branch¶
Show what branches contain a given sha¶
git merge¶
This lets you merge two branches.
Merge branch with master¶
Show all characters that are not valid in a git branch name¶
# everything before 33 and after 126 is invalid
for i in {33..126} ; do
char="$(printf "\\x$(printf '%02x' $i)")"
git check-ref-format --branch "foo-${char}-bar" >/dev/null 2>&1 || printf '%-4s %s is an invalid character\n' "$i" "$char"
done
The result is:
42 * is an invalid character
58 : is an invalid character
63 ? is an invalid character
91 [ is an invalid character
92 \ is an invalid character
94 ^ is an invalid character
126 ~ is an invalid character
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 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.
remotes¶
Add a remote¶
Push to a specific remote¶
Alter the source of origin¶
If you move your repo to another location, use this command to change the upstream URL:
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.
Hard reset of local changes¶
This will abandon all local changes and resolve merge conflicts
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
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¶
Aggressive repo state cleanup¶
This aggressively deletes all old refs and then does some garbage collection. On some repos, this can save a significant amount of space. I've been able to reduce some repo size by 92% using these commands:
git reset --hard "$whatever_point_you_want_to_be_at"
# now delete all branches and tags you don't want to include in your repo state
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
git grep¶
Find a string in all branches¶
This finds the word "hunter2" in the tests directory of all branches.
Exclude certain directories from git grep results¶
You can accomplish this using pathspec syntax.
See the pathspec definition in the git glossary for more info.