Tweaking Git Push



I recently investigated what terminal commands I used the most. Turns out git push is a common one. With that in mind it could make sense to investigate and solve some pain points I have with git push.

Pushing Tags

I commonly have to do both git push and git push -tags after a release. Ideally I would want it to push tags always at the same time.

My first attempt at a solution was using this git alias:

git config --global alias.p '!git push && git push --tags'

While that works it takes twice the time because you are actually contacting the remote server twice. Then I found a better answer on Stack Overflow: https://stackoverflow.com/questions/3745135/push-git-commits-tags-simultaneously

git config --global push.followTags true

It should be noted that this only pushes annotated tags and not lightweight ones. Luckily we use annotated tags where I work so that’s a non issue for me.

Making annotated tags takes some more writing. So let’s automate the tagging process. I use Maven so I can extract the version number and make the tag automatically by looking at the pom.xml:

function __gitutils_xpath {
    local query="${1}"
    local file="${2}"
    xmllint --format "${file}" 2> /dev/null | sed '2 s/xmlns=".*"//g' | xmllint --xpath "${query}" - 2> /dev/null
}

function __gitutils_xpath_pom {
    local query="${1}"
    __gitutils_xpath "${query}" "pom.xml"
}

function __gitutils_xpath_pom_project_version {
    __gitutils_xpath_pom "/project/version/text()"
}

function tag {
    local pomversion="$(__gitutils_xpath_pom_project_version)"
    if [[ $? -ne 0 ]]; then
        return 1 # False
    fi
    if [[ ! "${pomversion}" =~ ^[0-9]+.[0-9]+.[0-9]+$ ]]; then
        echo "Invalid Version: ${pomversion}"
        return 1 # False
    fi
    local tagname="v${pomversion}"
    git tag -a "${tagname}" -m "${tagname}"
}

The act of tagging a new release version now goes something like this:

git checkout master
git merge develop
tag
push

Sweet

Setting the Upstream

Do you also wish that setting the upstream was automatic?

The following Stack Overflow question provides a partial solution: https://stackoverflow.com/questions/6089294/why-do-i-need-to-do-set-upstream-all-the-time

We can configure Git to push to the current branch by default like this:

git config --global push.default current

You can now do git push but when you do git pull it will complain there is no upstream set. Now in theory an alias like this solves this by setting the upstream all the time:

git config --global alias.p "push -u"

That will however yield a spammy message about the upstream getting set every time you push. To remedy this we can make a more complex alias for push that sets the upstream only when it is not already set.

Step one is to create a script and set it as the push alias:

cd
touch git-push-alias.sh
chmod +x git-push-alias.sh
git config --global alias.p '!~/git-push-alias.sh'

Then we add the following code to our new file ~/git-push-alias.sh:

#!/usr/bin/env bash

upstream="$(git upstream)"
if [[ $? -ne 0 ]]; then
    exit 1
fi

if [[ -z "${upstream}" ]]; then
    git push -u "$@"
else
    git push "$@"
fi

The upstream will now automatically be set, but only when there is none to avoid log spam.

Sweet!

Scripts for complex aliases

Why use an executable script file for this git push alias? There are alternatives, but they are harder to work with in my opinion.

One alternative approach is the one described here: https://stackoverflow.com/questions/3321492/git-alias-with-positional-parameters

The idea is to create a function that you immediately run:

[alias]
    files = "!f() { git diff --name-status \"$1^\" \"$1\"; }; f"
  1. As you can imagine the quotes can quickly get out of hand.
  2. The readability from sane indentation disappears as you try to smash everything onto one line.
  3. You won’t get any syntax highlighting

So I propose using executable script files like previously mentioned for those reasons.

We can however do better than placing them directly in our home directory. Let’s create a dedicated directory for these scripts and add it to the path in .bashrc instead. It could go something like this:

cd
mkdir scripts

Then in your .bashrc add:

export PATH="${HOME}/scripts:${PATH}"

We could now create the previous script like this instead:

cd
cd scripts
touch git-push-alias.sh
chmod +x git-push-alias.sh
git config --global alias.p '!git-push-alias.sh'

This is just an example of course. If you have an existing dotfiles strategy in place, these scripts probably resides in there together with you git configuration.