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"
- As you can imagine the quotes can quickly get out of hand.
- The readability from sane indentation disappears as you try to smash everything onto one line.
- 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.