Helpful git aliases and config settings

I was about to reply to Why doesn’t Phoenix use Conventional Commit prefixes? - #41 by sodapopcan with some git aliases that I use for things like amending and rebasing commits, but then I thought it would be better to have a separate topic for that discussion, so here we are.

These are some git aliases I use frequently:

amend = commit -v --amend
fixup = !sh -c '_c=$(git rev-parse "$0") && git commit --fixup "$_c" && git rebase --autosquash --autostash --interactive "$_c"^ && unset _c'
force-push = push --force --force-with-lease
intend-to-add = add --intent-to-add
pop = stash pop

Most of these are for saving a few key strokes, for git commands I use extremely frequently (like git status -sb and git commit -v), I have fish shell abbreviations for those that are just 2-3 characters long.

The fixup alias above I find quite useful. It uses git rev-parse so that way the commit to be fixed can be specified by (partial) sha or something like HEAD~3, it’ll then commit what’s staged as a fixup commit and finally start an interactive rebase where most of the time nothing else needs to be done but to confirm.

I have other aliases for things like different formats of git log --all --graph but honestly I don’t use them that much.

Some other settings that I recommend:

[core]
  pager = $(brew --prefix git)/share/git-core/contrib/diff-highlight/diff-highlight | LESSCHARSET='utf-8' less -F -R -X
[branch]
  sort = -committerdate
[rerere]
  enabled = true
4 Likes

This creates a bash function g, for git, which defaults to git status when there are no arguments.

function g () {
    if [[ $# == 0 ]]
    then
        git status
    else
        git "$@"
    fi
}

4 Likes

You might like this GitHub - torbiak/git-autofixup: create fixup commits for topic branches

I alias it git af.

nit: You don’t need the --force here.

This is a good idea! I feel this should really be the default of git as showing help isn’t that helpful as most people know how to get help (and are probably looking online anyway).

My g is a function that creates a git repo:

g () { mkdir -p "$1" && cd "$1" && git init; }

I otherwise don’t have much to contribute here as I do the vast amount of my gitting in fugitive.vim. I have a couple of pretty uninteresting aliases like gs for git status (which happens also be my vim mapping to open the fugitive status window!) It finally bit me for the first last year when I actually wanted to experiment with ghost script for my job, but then I found out you can bypass aliases by prefixing them with a \ so it worked as a learning moment :slight_smile:

Oh! One little trick that it seems a lot of people don’t know (my data is my coworkers) is that instead of HEAD~N, you can just spam ^ which I find easier to type. So HEAD~3 can also be written as HEAD^^^. You can put as many as you want, it’s kinda funny in a way :smiley:

Also, HEAD doesn’t need to be written in caps anymore. It can also be written as @, though I find that more annoying to type on my keyboard than head.

1 Like

Yeah, I know, it’s a holdover from a time when you actually needed to have both flags, as --force-with-lease did not imply --force.

1 Like

Oh interesting, I did not know that!

If you use Oh My Zsh, there’s a gold mine of aliases at your fingertips.

Two personal aliases that I use all the time:
glm - See the commits for the current branch since it branched off from master.

alias glm='git log --oneline master..'

gccp - Copy the current (short) commit hash to the clipboard (pbcopy usually only on OSX, replace with xclip or whatever else you use if needed).

I use this when making changes on a Pull Request. It’s super helpful to respond to a comment and include the commit hash with makes the change being discussed. Github automatically turns this text (if the commit exists on GH) into a link to the commit, making it super easy for followup reviews, etc.

alias gccp="git rev-parse --short HEAD | tr -d '\n' | pbcopy"
3 Likes

Sorry I forgot to respond to this—I was too busy having a mini heart attack over the inline types thread :upside_down_face:

I’ve never used ommyzsh but I did used to have that mapping! I got rid of it once I realized I wanted to it across other branches as well. I generally don’t have very many aliases at but I do I have gl for git log which is quite long:

alias gl="git log --pretty=format:'%Cred%H%Creset%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

So now I just do gl main...

1 Like

Does shell completion work with these functions?

I never even thought to try but yes! Well, autocompletion doesn’t make sense for my g alias as I’m always creating a directory that doesn’t exist, but ya, I just tried gl and ya it all works.

I have other more advanced ones, but really once you get too complex, it is better to just have reference. I always forget them if the get too complex.



alias ga='git add'
alias gb='git branch'
alias gc='git checkout'
alias gcb='git checkout -b'
alias gca='git commit --amend'
alias gcme='git commit -m '
alias gd='git diff'

From .bash_profile:

source ~/.git-completion.bash
__git_complete g __git_main # Makes git completion work with 'g' alias

Ah nice, not having working shell completion is what bothered me the last time I tried having functions like these.

Ya, forgetting them is the main reason I don’t like a lot of aliases. Holding a bunch of acronyms in my head is hard for me. I also don’t like forgetting how to do the actual thing (if possible).

Another reason I just realized because it’s so ingrained, which is also a reason I never thought about bash completion (beyond file names), is that I use zsh-autosuggest which gives fish-style completions which I love. Couple that with zsh-syntax-highlighting for the full effect! With the latter you don’t even really need which/command -v anymore—if a command is green, you’re good!

2 Likes

This is something I really like about fish abbreviations: they expand from the abbreviated form to their full form either on <enter> (to run as is) or on <space> (to adjust the command before running).

2 Likes

I use this one, it comes with ohmyzsh git I think :smiley:

❯ alias gcssm
gcssm='git commit --gpg-sign --signoff --message'

I have quite a few aliases and config recommendations. My personal favorite is git oops.

Tip: Patch Mode

Neither config nor alias per-se, but very useful: I do almost all my staging of changes hunk-by-hunk rather than file-by-file with git add -p (--patch). Check it out if you’ve never used it before! It allows me to craft much more intentional commits from a messy work tree.

It especially makes it really easy to do things like add temporary imports/requires/aliases for debugging purposes, and log/debug statements, in your code without accidentally committing them, or having to unwind them for committing purposes.

There are quite a few options when adding in “patch” mode, ? will give you some context. Over time I’ve incorporated y,n,q,a,d,j pretty deep into my muscle memory and workflow, but it takes some time and just y/n/q is a good start. For the purpose of subdividing hunks in order to separate a debug log line from some relevant code you want to commit, use s.

Already use git add -p? Did you know that --patch also works with:

  • git reset to selectively un-stage changes from files in the index into the work tree
  • git restore to selectively discard changes from files in the work tree
  • git stash to selectively stash changes from files out of the work tree

Config

A lot of my config is fairly specific to my own preferred git workflows, but these are workflow-agonistic, useful for anyone, and in my opinion should be defaults.

  • rerere.enabled = true

    Reuse Recorded Resolution. Better ink than mine has been spilled on this, but essentially, if you ever rebase at all, you want this set. It makes dealing with merge conflicts while rebasing palatable.

    git config set --global rerere.enabled true
    
  • merge.conflictstyle = diff3

    Adds a third section to merge conflicts, showing what the code looked like before either conflicting commit touched it. This gives essential context to what each author was trying to accomplish when changing the code, making it much more trivial to reconcile.

    git config set --global merge.conflictstyle diff3
    
  • diff.mnemonicprefix = true

    Uses helpful prefixes rather than the default a/file/name and b/file/name when diffing between things, where at least one thing is not a commit. Makes it easier to remember halfway scrolling through a long diff if you are comparing between a commit (c/), the index (i/), or the work tree (w/); or comparing two arbitrary commits (back to a/ and b/).

    Useful if you often run git diff with various arguments (git diff, git diff <ref>, git diff --cached).

    git config set --global diff.mnemonicprefix true
    

Aliases

I also don’t care for the git alphabet soup shortcuts many folk use, it makes it harder for rich tab-completion and command history tools to work. I type just fast enough that using the full form doesn’t really slow down common operations, and think just slowly enough it wouldn’t speed up uncommon ones. I also tend to make more typos mashing out a short vowel-less alias and jamming Enter than I would composing a full command using things closer to full words.

Utility

  • git alias

    An alias that lists all aliases.

    git config set --global alias.alias "!alias(){ git config --get-regexp '^alias.${1}'; }; alias"
    
  • git cherrypick

    Because I forget the - in cherry-pick, every time.

    git config set --global alias.cherrypick cherry-pick
    

Informational

  • git current

    Shows the current local branch.

    Useful for scripting purposes, prompt display, or command substitution.

    For example, when pushing up a branch for the first time, git push -u origin $(git current).

    git config set --global alias.current rev-parse --abbrev-ref HEAD
    
  • git last

    Shows a rich display of the last commit.

    Customizable using pretty formats.

    git config set --global alias.last "log -n1 --pretty='format:Commit: %C(yellow)%H%nAuthored by: %C(magenta)%aN %Creset%ar%nCommited by: %Cblue%cN %Creset%cr%n%n%B'"
    
    
  • git latest [N]

    Shows a rich summary of the last N commits, default 5.

    Customizable using pretty formats.

    (I use --- >8 --- to separate commits because I also use commit.cleanup = scissors and it pleases me.)

    git config set --global alias.latest "log -n${1:-5} --pretty='format:%Cgreen---------------------- >8 ----------------------%nCommit: %C(yellow)%H%nAuthored by: %C(magenta)%aN %Creset%ar%nCommited by: %Cblue%cN %Creset%cr%n%n%s%n'"
    
  • git contains <ref>

    Lists the branches a <ref> is in.

    Performs a fetch first to ensure you are up-to-date with, ex, PRs being merged into mainline while working on your branch.

    git config set --global alias.contains "!contains(){ git fetch && git rev-parse ${1:-HEAD} | xargs git branch -r --contains; }; contains"
    
  • git ancestor <branch1> [<branch2>]

    Shows the last commit shared between two branches. Uses HEAD if only one branch is provided.

    git config set --global alias.ancestor merge-base --octopus
    

History Modification

  • git oops [-m "new message"] [-a | list/of/files]

    Add something you forgot to the latest commit.

    Any staged changes in the index are folded into the last commit you made. Other unstaged changes in the work tree can be added by listing files, or all unstaged changes can be added with -a.

    The commit’s message can be changed with -m, but unlike standard commiting, you will not be presented with an editor if none is provided: the existing message will be left as-is.

    git config set --global alias.oops commit --amend --no-edit
    
  • git rollback [N]

    Soft-resets the latest N commits, default 1.

    All changes in rolled back commits are preserved, staged into the index.

    git config set --global alias.rollback "!rollback(){ cd ${GIT_PREFIX:-.} && git reset HEAD~${1:-1} --soft; }; rollback"
    
4 Likes

add -p is one of those things you think couldn’t possibly be usable as you think you need more context but turns out it’s really intuitive. The vast majority of folks at an old job used it.

All of these are great but, well, I’ll sound like a broken record but I just do most of this stuff in fugutive.vim.

That’s interesting you prefer soft reset—I did that for years and removed the --soft after realizing I was essentially always doing git reset --soft head^ && git reset (although not exactly like that) which is of course just git reset head^. Is there are part of your workflow that you’d rather keep them staged? I’m guessing it’s about getting new diff markers for new changes after reset?

I’ve had rerere set for years and I don’t think I ever knew what it did :sweat_smile:

+1 for git oops :grin:

1 Like

Yup, pretty much.

I have another alias (git undo) for unwinding commits that is the same as rollback N, using the default (--mixed) reset mode. I also have a git erase for --hard resets. With my workflow, I tend to find myself using rollback the most often by far, so that’s the one I share.

The main reason why I want to ‘unwind’ one or more (usually fairly intentionally crafted) commits is because I have ongoing work that leads me to discover that previous work was partially incorrect or incomplete. A --soft reset helps me partition a) the work previously committed (into the index) from b) the work I was doing in the work tree that led me to discover that I want to unwind some commits. Othertimes, I’ll unwind commits because they were intentionally temporary and exploratory, in which case I’m preparing to finalize some of their contents into an intentionally crafted commit, so placing them into the index ready to commit saves me a step.

Then I’ll usually use add/reset/restore/stash --patch modes to “rebuild” the previously committed work in the index into a proper committable state: shifting hunks between the old commits, my dirty worktree of discovery, the stash for things I might want to come back to, and nonexistence.

Often only some of the contents of my dirty worktree need to take part in this dance when I start it; so --mixed would muddle what I was unwinding with what I’ve discovered whereas --soft leaves them partitioned.

2 Likes