􀀂􀀟􀀍􀀅 􀀂􀀚􀀃 git back

A useful custom git subcommand

Have you ever had the need for a commit to be timestamped with some time other than the moment you typed git commit? Perhaps you were supposed to have done some work yesterday, or weren’t supposed to have done some work until after yesterday, and you’d like git log to reflect the, ahh, official truth.

Introducing git back

(With apologies to recovering nu metal fans. 1)

To set a specific point in history for git commit, you’d think you can just use the --date argument , but this only sets the author date, per git commit --help:

--date=<date>
    Override the author date used in the commit.

… Wait, “only” sets the author date? Indeed. There is also the commit date. Instead, the script below takes care to set both author and commit dates via environment variables. This means it works not only with git commit, but also with git merge, which doesn’t take --date argument. You must enter the date in normal Git format, which you can see via git back --help or just refer to the dates in git log.

Did you know you can make a program called git-SOMETHING anywhere in your $PATH, and then run it as git SOMETHING? It feels like it’s just part of git. In fact, much of git itself is built this way!

Drop this program into /usr/local/bin/git-back (or wherever is appopriate for your environment) and use history to tell whatever story you want, just like god intended.

git-back source code (silent version)
#!/bin/sh

set -e

cmdname=`basename "$0"`

usage() {
    cat << ENDUSAGE
git-back: [-h|--help] <date> <args...>
Run a git command as if on a certain date.

ARGUMENTS

    date: A date in git format, like "Sun Aug 30 19:48:32 2020 -0500"
    args: Arguments to send to git.

EXAMPLES

    git-back "Sun Aug 30 19:48:32 2020 -0500" commit -a -m "Fix bug #123"
    git-back "Sun Aug 30 19:48:32 2020 -0500" merge upstream/master

IMPLEMENTATION

    All this does is set the GIT_COMMITTER_DATE and GIT_AUTHOR_DATE
    and run a git command.

    Note that running the simple 'git commit --date <date>' is not sufficient,
    as it only sets one of those values.
    There is also no --date option for 'git merge', which creates a commit.

    The final word on date formatting comes from the
    https://github.com/git/git/blob/master/Documentation/date-formats.txt

ENDUSAGE
}

if test $# -lt 1 || test "$1" = "-h" || test "$1" = "--help"; then
    usage
    exit
fi

date="$1"
shift

export GIT_COMMITTER_DATE="$date"
export GIT_AUTHOR_DATE="$date"
git "$@"

Most of the script is actually just its help, which I will also copy here:

> git back --help
git-back: [-h|--help] <date> <args...>
Run a git command as if on a certain date.

ARGUMENTS

    date: A date in git format, like "Sun Aug 30 19:48:32 2020 -0500"
    args: Arguments to send to git.

EXAMPLES

    git-back "Sun Aug 30 19:48:32 2020 -0500" commit -a -m "Fix bug #123"
    git-back "Sun Aug 30 19:48:32 2020 -0500" merge upstream/master

IMPLEMENTATION

    All this does is set the GIT_COMMITTER_DATE and GIT_AUTHOR_DATE
    and run a git command.

    Note that running the simple 'git commit --date <date>' is not sufficient,
    as it only sets one of those values.
    There is also no --date option for 'git merge', which creates a commit.

    The final word on date formatting comes from the
    https://github.com/git/git/blob/master/Documentation/date-formats.txt

Audio upgrade

But what if you demand more from your custom git subcommands? What if you want to hear the name of the command yelled into the microphone by the lead singer of a rock band that was formed almost thirty years ago? What if you want to be surprised (and, dare I say, delighted?) every time you fabricate your commit history?

I, of course, have you covered.

git-back source code (upgraded with audio)
#!/bin/sh

set -e

cmdname=`basename "$0"`

usage() {
    cat << ENDUSAGE
git-back: [-h|--help] <date> <args...>
Run a git command as if on a certain date.

ARGUMENTS

    date: A date in git format, like "Sun Aug 30 19:48:32 2020 -0500"
    args: Arguments to send to git.

EXAMPLES

    git-back "Sun Aug 30 19:48:32 2020 -0500" commit -a -m "Fix bug #123"
    git-back "Sun Aug 30 19:48:32 2020 -0500" merge upstream/master

IMPLEMENTATION

    All this does is set the GIT_COMMITTER_DATE and GIT_AUTHOR_DATE
    and run a git command.

    Note that running the simple 'git commit --date <date>' is not sufficient,
    as it only sets one of those values.
    There is also no --date option for 'git merge', which creates a commit.

    The final word on date formatting comes from the
    https://github.com/git/git/blob/master/Documentation/date-formats.txt

ENDUSAGE
}

# Download $HOME/.git-back.mp3 if it doesn't exist.
godsmack_dl() {
    if type youtube-dl >/dev/null && type ffmpeg >/dev/null; then
        if ! test -e "$HOME/.bad-religion.full.mp3"; then
            youtube-dl --extract-audio --audio-format mp3 -o "$HOME/.bad-religion.full.mp3" 'https://www.youtube.com/watch?v=SS7bnB11QQ4'
        fi
        if ! test -e "$HOME/.git-back.mp3"; then
            # -ss is start time; -t is duration of clip
            ffmpeg -ss 0.25 -t 3.5 -i "$HOME/.bad-religion.full.mp3" "$HOME/.git-back.mp3"
        fi
    fi
}

# Play $HOME/.git-back.mp3 if it exists and you have a player installed.
godsmack_play() {
    player=
    for cmd in afplay mplayer mpg123 mpg321; do
        if type $cmd >/dev/null; then
            player=$cmd
            break
        fi
    done
    if test -e "$HOME/.git-back.mp3" && test "$player"; then
        $player "$HOME/.git-back.mp3" &
    fi
}

godsmack_play

if test $# -lt 1 || test "$1" = "-h" || test "$1" = "--help"; then
    usage
    exit
fi

if test "$1" = "godsmack"; then
    godsmack_dl
    godsmack_play
    exit
fi

date="$1"
shift

export GIT_COMMITTER_DATE="$date"
export GIT_AUTHOR_DATE="$date"
git "$@"

Replace the previous, silent version of git-back with this new upgraded version, and then run git back godsmack just once. After that, git back will loudly remind you what command you just typed each time you run it.


  1. Related: did you know↩︎

Responses

Comments are hosted on this site and powered by Remark42 (thanks!).

Webmentions are hosted on remote sites and syndicated via Webmention.io (thanks!).