Skip to content

Git

baddass version control

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.