Skip to content

Instantly share code, notes, and snippets.

@rossjrw
Last active May 21, 2024 09:23
Show Gist options
  • Save rossjrw/5737e5210aa9bd0d6bf48df546646227 to your computer and use it in GitHub Desktop.
Save rossjrw/5737e5210aa9bd0d6bf48df546646227 to your computer and use it in GitHub Desktop.
Git + Docker cheatsheets
These are my cheatsheets for Git and Docker.
I will update this with cheats that I'd like to remember, whenever I remember that this list exists.

Docker cheatsheet

Inspecting a container's filesystem

Useful if all of the following are true:

  • Want to inspect a container's contents, not an image (e.g. to view files generated during the container's execution that aren't in the image originally)
  • The container does not run a continuous process (if run, will probably shut down while you're still inside it)
    • If it does, use docker exec -it [name] /bin/sh to start a shell inside it directly

Create a snapshot:

docker commit [container id] [snapshot name]

Inspect the snapshot:

docker run -it --rm [snapshot name] /bin/sh

Remove the snapshot:

docker image rm [snapshot name]

Git cheatsheet

Git status

Since learning about git status it is approximately one third of my terminal commands. (ohmyzsh: gst)

What are branches?

There's nothing special about them. They're just pointers to a commit. The 'branch' as you imagine it visually is just two (or more) commits who both think they have the same parent. There's nothing stopping you from creating that situation where the 'branch' doesn't have any refs pointing to it, in which case it will eventually be garbage collected.

Initialise a PR

I like to have an empty PR where I can draft my proposal and set myself a series of tasks/requirements before I start any work at all. GitHub won't let me make a PR on a branch without any commits, so I can make an empty commit:

git checkout -b feature
git commit -m "Init PR" --allow-empty
git push origin feature

(ohmyzsh: gcb feature, gcmsg "Init PR" --allow-empty, gpsup)

I like to say "Init PR" for personal projects, but for team projects it's probably more traditional to go with "Initial commit".

Unless I want that empty commit to appear in the pull request until the end of time, it's probably a good idea to make the next commit an amend commit. Alternatively, I can just run interactive rebase later, and the empty commit wil automatically be dropped (it will be commented out by default in the todo list).

Worth noting that this whole kerfuffle is only necessary for GitHub. On GitLab you can just make an MR (merge request - GitLab terminology) without any commits and it's totally chill about it.

Trial changes from another branch

If I want to use changes in branchB to test changes in branchA, without merging branchB into branchA because while they're obviously dependent they're unrelated features, I can trial the merge:

git merge --no-commit --squash branchB

branchB will be merged into branchA and the changes will be staged, but not committed.

Then to undo:

git reset HEAD

A single reset is fine because the commits were squashed into one.

Track branches automatically

If I want to be able to git push and git pull on a branch without having to specify git push origin branchname, I need to set it to track the corresponding branch on origin. The best way to do that is to add -u the first time I push the branch to origin:

git push origin branchname -u

(ohmyzsh: gpsup)

From then onwards, the branch is automatically tracked. This also enables powerlevel10k indicators that show how many commits my local branch is ahead/behind compared to origin.

Patch adding

If I've been naughty and accumulated a bunch of changes that should be in several different commits, and if those commits aren't cleanly divided between files for easy separation with git add, I can use git add -p to select by change rather than by file.

(ohmyzsh: gapa)

Patch removing

To remove staged changes from the index patchwise: git reset -p

To remove unstaged changes from the working tree patchwise: git checkout -p

Patch reverting

To revert changes made in a previous commit patchwise (to i.e. partially revert a commit):

git revert <bad-commit> --no-commit
git reset
# Edit file/s or patch remove
git add
git checkout .   # Clear unstaged changes
git commit -m "Partially revert <bad-commit>..."

Bisect

git bisect is a lifesaver for finding out when bugs were introduced.

Doesn't need to be a bug - any question of the form 'what the first commit where X?' can be answered by bisect.

  1. git bisect start - start bisecting
  2. git bisect bad [ref] - mark a bad commit that contains the bug
  3. git bisect good [ref] - mark a good commit from before the bug was introduced
  4. Bisect will show me a bunch of commits. Check if the bug is present and communicate that with git bisect good and git bisect bad. Best to write a test script for it and leave it untracked. Git will show me which commit the bug is first present in.
  5. git bisect reset - go back to wherever I was before I started bisecting

E.g. for an automated bisect that just checks the entire history...

git bisect start HEAD $(git rev-list --max-parents=0 HEAD)
git bisect run sh -c "some command"

The command could be e.g. rg text, where a commit is considered good if it contains the text (or ! rg text to consider it good only if it does not)

Filter repo

https://github.com/newren/git-filter-repo

This is some good stuff

Oh My Zsh

Git command shortcuts when using Oh My Zsh: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git

Move branch to here

git switch -C <branch-name> <destination>

Fixup

Amend the last commit: git commit --amend and optionally --no-edit (OMZ: gc! / gcn!)

Amend the commit with hash X: git commit --fixup=X, then git rebase -i --autosquash HEAD~N where N is at least the number of commits between HEAD and X

--autosquash just causes the special commit-message commands created by --fixup to automatically edit the interactive rebase's todo list, but doesn't actually mutate anything.

.gitignore magic

To ignore a directory without matching files of the same name, append /.

To ignore a path without globbing (e.g. to ignore only a file in the root dir and not files elsewhere with the same name), prepend /.

To ignore a file without adding it to .gitignore, e.g. a personal utility script or symlink that nobody else needs to know about, add it to .git/info/exclude instead. gitignore syntax works here too.

Pull without checkout

To pull another branch without first checking out that branch: git fetch origin <branch>:<branch>

e.g. git fetch origin develop:develop is equivalent to

git stash
git checkout develop
git pull
git checkout -
git stash pop

Show changes from this branch only

For a branch that's had the main branch merged into it a few times, git log loses its meaning as it starts to fill with unrelated commits.

git log --no-merges branchname ^main will filter those commits out.

Fixups

git rebase --interactive --autosquash automatically sets fixup commits to fixup the commit that they target.

--autosquash can be applied to grbi by default with git config --global rebase.autosquash true.

A fixup commit can be created with git commit --fixup=SHA, or if the SHA is unknown (i.e. you are too lazy to open the log) but you remember a sufficiently-unique part of the commit message, git commit --fixup=:/message will work too.

Just remember to rebase before pushing.

Stashing

git stash --include-untracked (gstu) is good for not having untracked files follow you across branches.

Use commit message from another commit

git commit -C [ref] uses the commit message from another commit. Useful for recovering commit messages when rebasing etc.

Finding the merge base

To find the common ancestor of two refs:

git merge-base <ref> <ref>

e.g. git merge-base HEAD develop.

This can be useful when resolving merge conflicts in a file, and you want to know what changes were actually introduced in both sides of the conflict (instead of just seeing the net effect of both changes):

git diff $(git merge-base ref1 ref2) ref1 -- relevant_file
git diff $(git merge-base ref1 ref2) ref2 -- relevant_file

Here's a version that does the above for the two parent commits, while run during a merge conflict:

git diff $(git merge-base $(git rev-parse HEAD) $(git rev-parse MERGE_HEAD)) $(git rev-parse HEAD) -- relevant_file
git diff $(git merge-base $(git rev-parse HEAD) $(git rev-parse MERGE_HEAD)) $(git rev-parse MERGE_HEAD) -- relevant_file

There's probably some nice way to alias this to git conflicts or something, but I've not worked that one out.

GitHub: Add commits to a contributor's pull request

Add the contributor's repo as a remote and fetch its changes:

git remote add <their name> <their repo URL>
git fetch <their name>

You can't checkout branches in a remote directly - you have to make a local copy of them.

git checkout -b <your branch name> <their name>/<their branch name>

Note that <their name> in the above command is actually the name of the remote (as defined in the first command), hence the slash.

Then to push: (think "git push origin master", but with a different remote)

git push <their name> <your branch name>:<their branch name>

The above can be simplified if your branch name and their branch name match, but if you wanted to e.g. namespace your branch name with their actual name, that won't be the case.

Purge stale branches

To remove branches that have accumulated locally but were deleted on the remote:

git for-each-ref --format '%(refname:short) %(upstream:track)' | awk '$2 == "[gone]" {print $1}' | xargs -L1 -r -p git branch -d

xargs -p will prompt for a Y/N for each delete.

Based on https://www.erikschierboom.com/2020/02/17/cleaning-up-local-git-branches-deleted-on-a-remote/

Then, to prune local refs of remote branches:

git remote prune origin

Append --dry-run to see what will be deleted.

Merging when the origin branch was squashed

Situation:

Colleague is working on feature-A. You start work on related feature building on that, feature-B. You branch feature-B from feature-A and commence work. Colleague completes work on feature-A and squash-merges it. This creates a squash commit on the merge target branch containing the work but none of the history of feature-A. You try to merge feature-B into the merge target and there are one million conflicts between the squash commit and the old feature-A commits in your feature-B branch.

Solution: git rebase --onto <merge target> feature-A feature-B. All merge conflicts during rebase should be between feature-A work and feature-B work (which, having worked on feature-B, you should be able to resolve yourself) rather than between feature-A work and old feature-A work.

See a file at a commit

git show <commit>:<file>

Reset a local branch to be the same as its remote counterpart

git reset --hard @{u}

This is a specific example of the gitrevisions mini-language: https://www.git-scm.com/docs/gitrevisions

In this case @ is a shortcut for HEAD, and u is a shorthand for upstream. @{u} effectively expands to 'the upstream counterpart of whatever is currently checked out'.

Git keeps asking for auth creds

If the Git server has your SSH key and you keep trying to pull it over HTTP(S), it'll keep asking for your creds instead of using that key. Fix it by making it use SSH

Look in .git/config for:

[remote "origin"]
        url = https://<server>/<repo>

Replace with:

[remote "origin"]
        url = git@<server>:<repo>

To avoid this in future, when cloning a repo, use the SSH URI rather than the HTTP(S) URL

Dot dot dot

git diff A..B finds commits in B that aren't in A

git diff A...B finds commits in B that aren't in A, and commits in A that aren't in B

E.g.:

A - B - C - D - E
         \
          F - G

git log E..G gets you F and G

git log E...G traces the path from E to G, returning all commits along the way, except for C which is in both histories

stat of changed files

git status --porcelain | awk '{print $2}' | xargs ls -al -d 2>/dev/null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment