Handling Go modules replacement directive version lag and fork maintenance
Tags: go, modules
Published: 2020-06-11 15:11:22

---
title: Handling Go modules replacement directive version lag and fork maintenance
published: true
description: Handling Go mod replacement directive version lag for fork
tags: #golang #go #modules
//cover_image: https://direct_url_to_image.jpg
---
At Khan Academy, we have a [public fork](https://github.com/Khan/golangci-lint) of a [public repo](https://github.com/golangci/golangci-lint), which we are carrying a patch for in a [branch](https://github.com/Khan/golangci-lint/tree/custom-autofix). The latest release tag in the upstream is 1.27.0

In our other repositories, we have a replace directive:
```
replace github.com/golangci/golangci-lint => github.com/Khan/golangci-lint v1.21.1-0.20200609223024-b6a17227ca75
```
Even though the date and git commit SHA1 portion were correct, we found the pseudo-version prefix of `v1.21.1` continuing to lag the upstream `v1.27.0` confusing, and we were wondering what actions we could do in our public fork to be able to get that to update without running afoul of the dreaded "version before v1.27.0 would have negative patch number" or "unknown revision" errors.

It turns out that it was because the branch with our patch was __merging__ upstream changes, rather than __rebasing__. This meant the last common tag between upstream and our fork's branch *was* `v1.21.1`, not `v1.27.0`. Darn computers always just doing what you asked them, right?

So to fix this we had to rebase against the upstream's master's up to the last release commit (we don't want to use unreleased code). This is a little tricky to get correct, so here's the recipe in glorious bash:

```
export WORKING_BRANCH=custom-autofix
export REBASED_BRANCH=autofix-custom
git clone git@github.com:Khan/golangci-lint.git
cd golangci-lint
git remote add upstream git@github.com:golangci/golangci-lint.git
git pull upstream master
git fetch --tags upstream
# Push the tags from my local to our fork's master branch (not sure if required)
git push -f --tags origin master
git checkout $WORKING_BRANCH
git co -b $REBASED_BRANCH
# Rebase against upstream latest tag
git reset --soft $(git log $(git tag | sort -V | tail -1) -1 --pretty=%H)
git commit -s -S -a
git push origin $REBASED_BRANCH -u
export LATEST_GOLANGCI_COMMIT=$(git log -1 --pretty=%H)
# Ta-da! All done. Now go to our private repo that uses this:
cd ~/khan/privaterepo
go mod edit -replace=github.com/golangci/golangci-lint=github.com/khan/golangci-lint@${LATEST_GOLANGCI_COMMIT}
# This will create/update the replace directive to github.com/Khan/golangci-lint v1.27.2-0.20200610182323-d6b2676ecc9b
# I could also have used the branch name instead of commit SHA1.
```
### WTF was all that about?

Now there's some stuff in that bash-fu to unpack:
```
git tag | sort -V | tail -1
```
This will give the latest tag, which also will be upstream's latest tag unless you've tagged a release in your fork with a semantic version that's later (tip: *Don't* do that).

```
git log $(git tag | sort -V | tail -1) -1 --pretty=%H
```
This will give the full commit SHA1 of the latest tag.

### Wait, why does is it start with `v1.27.2` instead of `v1.27.0` or `v1.27.1` ? 
`go mod` will take the fork's latest release tag's semver and increment the patch number by one (because your replacement fork *is* a patch, see?).

The incrementing the semver prefix is the only thing wrong with calculating it by hand in your fork using:
```
echo "$(git tag | sort -V | tail -1)-$(git --no-pager show\
  --quiet\
  --abbrev=12\
  --date='format-local:%Y%m%d%H%M%S'\
  --format="%cd-%h")"
# output: v1.27.1-20200609183024-b6a17227ca75
```
To test things out, I made a newer release tag than upstream in our fork, `v1.27.1`, and `go mod` chose `v1.27.2` which tells me it's not just looking at upstream's tags. It seems less confusing to me to just keep syncing upstream's tags to your fork.

Ok, I don't know if that is useful to anyone else!