I've been thinking about the vulnerability of the primary Git branch for the last several weeks. Mostly out of paranoia about destroying a critical application. I added protective measures to my local clones on important projects and was content in thinking that I was now safe. But today I was reminded that this is only a small part of protecting a collaborative project.

Here's what happened:

  1. User 1 made a commit on master and pushed to origin
  2. User 2 fixed a bad merge on branch feature and ran git push --force
  3. User 1 made a tag directly on the remote and deployed to Production
  4. User 1 saw the new tag in production, but the new commit was missing

The problem was introduced with the git push --force. User 2 must have had a Git push configuration of default = matching. This is the default in Git version pre-2.0, but can still be set explicitly on newer versions. The result was that the intended feature branch was force pushed, AND the master branch was also force pushed!! Yikes.

We lucked out, as the newly deployed tag was identical to the previous tag and there was no production impact. But the result could have been much, much worse.

Here's what we're doing about it:

  1. Add local force push protection for the master branch with a pre-push Git hook and push config enforcement
  2. Add protection to the master branch of our Github mirror if possible

We already use a git-setup.sh script to add a pre-commit static code checker, so it should be pretty quick to add a pre-push hook from the following Gist:

The one case this hook doesn't cover is the exact scenario described above. In order to protect against this, we can add local Git configuration to ensure the default push behavior as anything other than matching. My current preference is for nothing, but really any of simple, current, or upstream should work fine.

We use Github primarily for code reviews and mirror our repo there via Jenkins. It's not clear whether branch protection is compatible with mirroring via Jenkins, so that'll need some more research.

Over the weekend, I've been cleaning up, organizing and improving my dotfiles. Below are a couple of things that I'm most excited about.

Zsh in Vim Mode

I've been using zsh for a while, but only recently starting using zsh in Vim mode. One thing that's been sorely missing is moving by word in INSERT mode. I got this working by adding the following to ~/.zshrc:

bindkey '^b' backward-word
bindkey '^w' forward-word

With this config loaded, I can move forward by word boundaries with Control-w and backward with Control-b. Lot's more configurations are listed here.

Git Advanced Aliases

I've been assembling Git shortcuts into a separate "functions" dotfile, but learned that it's maybe cleaner to use Git advanced aliases. You can read more about advanced aliases here.

The advanced alias I'm most excited about is triggering a post-push hook, which is not provided in Git core. The below solution is adapted to a Git advanced alias from this Stack Overflow thread:

  # Wrapper for git push to enable a post-push hook
  push-with-hooks = !"f() { \
      local GIT_DIR_="$(git rev-parse --git-dir)" \
        && local BRANCH="$(git rev-parse --symbolic --abbrev-ref $(git symbolic-ref HEAD))" \
        && local POST_PUSH="$GIT_DIR_/hooks/post-push" \
        && git push "$@" \
        && test $? -eq 0 && test -x "$POST_PUSH" \
        && exec "$POST_PUSH" "$BRANCH"; \
    }; f"

With the above in place, put the following in an executable file at .git/hooks/post-push:

#!/bin/sh

echo "$@"

This doesn't do much as-is outside of echoing the current branch to STDOUT, but could be used to trigger a build, or all kinds of fun things!

Today I needed to perform multiple transforms on a chunk of text in a file and found a nice solution in Vim! Initially I was thinking about VISUAL mode in Vim and chaining transforms the way I would do with sed (i.e. s/one/two/g;s/^/ /;s/$/,/). It turns out this doesn't exactly work in Vim, but it's not too far off the mark.

The following syntax works in Vim NORMAL mode:

:%s/^/  / | %s/string/replace/g | %s/$/,/

And in VISUAL mode, the sytax is slightly different. Select a bunch of lines to operate on, and then use the following:

:'<,'>s/^/  / | :'<,'>s/string/replace/g | :'<,'>s/$/,/

There are some great resources for getting set up with signing Git commits and tags with a GPG key:

After following instructions, I still got the following error:

error: gpg failed to sign the data
fatal: failed to write commit object

Below is a sequence of commands that got everything working properly:

# Assumes homebrew and existing key-pair
brew install pinentry-mac
# Get the secret key value
gpg2 --list-secret-keys | grep ^sec
git config --global user.signingkey {secret-value}
git config --global gpg.program $(which gpg2)
# Sign everything by default
git config --global commit.gpgsign true
echo "no-tty" >> ~/.gnupg/gpg.conf
echo $(which pinentry-mac) >> ~/.gnupg/gpg-agent.conf

When you next sign a tag or commit, you'll get a dialog asking for the password to your GPG key, and assuming all is well everything should complete nominally.

Below is a smattering of helpful tidbits from the last several months.

less

Case-insensitive search: Add -i to your LESS environment variable

Drupal

Hide fieldgroups with all child fields: field_group_hide_field_groups($form, array('field_group_name'));

OS X

"Disable" the dock by setting a long autohide delay: defaults write com.apple.dock autohide-delay -float 5

Clear the clipboard: pbcopy </dev/null

wc

Consider the following:

# 'HELLO' + newline
echo HELLO | wc -m
6
# Just 'HELLO'
echo -n HELLO | wc -m
5

Google Hangout

  • Toggle camera: ⌘ + e
  • Toggle microphone: ⌘ + d

Much better than using a mouse!

Vim

  • Toggle the case of characters in a word: g~iw.
  • Show all whitespace characters: :set list
  • Replace a character with a newline: Use \r instead of \n
  • Delete all lines that match a pattern: :g/pattern/d