git --commit fixup <SHA> is used to note that the commit you are making should be squashed into SHA.

When using the default git settings, the process of putting a new file into an older commit looks like this (assuming we want it to be in abcdefg):

  • make changes
  • git add changes
  • git log to manually find the SHA you’re looking for
  • git commit --fixup abcdefg
  • git rebase --autosquash --autostash -i1 2

The unfortunate step is having to go through the git log and copy the SHA then paste it into the next two commands.

git log --pretty=oneline master.. will print us all the commits we have on our current branch that do not exist on master.

We can pass this to fzf to easily pick the commit that we want to squash into.

$(git log --pretty=oneline master.. | fzf)

This is essentially what I used until I discovered fzf’s --preview option.

The command we will use to preview is git show --color=always --pretty=fuller --stat SHA. This will show us some basic info about the commit as well as which files were modified inside it. This will allow us to easily determine where we want to place our squash.

Putting it all together, we get this: $(git log --pretty=oneline master.. | fzf --preview "echo {} | cut -f 1 -d' ' | xargs -I SHA git show --color=always --pretty=fuller --stat SHA" | awk '{ print $1 }')

We use cut and xargs here so that we can manipulate the selected line to return only the SHA.

Here is the full function that works with fzf and fzf-tmux and also determines the base branch (develop if it exists, otherwise master):

cfu() {
  target=$(git log --pretty=oneline $(base_branch).. | $(fzf_prog) --preview "echo {} | cut -f 1 -d' ' | xargs -I SHA git show --color=always --pretty=fuller --stat SHA" | awk '{ print $1 }')

  if [[ $target != '' ]]; then
    git commit --fixup $(echo $target)
  fi
}

It depends on these two functions:

fzf_prog(){
  if [ "$TMUX" = "" ]; then
    echo "fzf"
  else
    echo "fzf-tmux"
  fi
}

base_branch() {
  if git rev-parse -q --verify develop > /dev/null; then
    echo "develop"
  else
    echo "master"
  fi
}

While it is possible to bring the rebase command into the function I prefer to not. I would rather decide when the rebase happens.

1 See autostash and autosquash

2 I prefer a full interactive rebase so that I have a chance to review before committing.