Subject: How to publish a fork

From: Sebastian Tennant <[email-masked]>
Date: Fri, 26 May 2023 11:13:49 +0000

Hello list,

I wish to publish a fork of an upstream git repository (as a means of
distributing modifications to upstream across multiple machines).

Let's call the publishing repo fork.git, the machine which hosts
fork.git the fork host, and the machines which clone fork.git clients
of the fork host.

fork.git includes unmodified upstream branches and additional branches
not found upstream.  The additional branches all share a common
namespace; mod-*.  For example, mod-master is an additional branch
based on upstream branch master.  A cron job running on the fork host
automatically fetches from upstream every 30 minutes using a mirror
refspec, e.g. +refs/heads/master:refs/heads/master.  Clients of the
fork host also use a mirror refspec for upstream branches and an
ordinary fetch refspec for the additional branches,
e.g. +refs/heads/mod-master:refs/remotes/forkhost/mod-master.

To summarise, the resulting configs look something like this:

Fork host config (extract):
--8<---------------cut here---------------start------------->8---
[remote "upstream"]
	url = https://example.com/upstream.git
	fetch = +refs/heads/master:refs/heads/master
--8<---------------cut here---------------end--------------->8---

Fork host client config (extract):
--8<---------------cut here---------------start------------->8---
[remote "forkhost"]
	url = https://fork.host/fork.git
	fetch = +refs/heads/master:refs/heads/master
	fetch = +refs/heads/mod-master:refs/remotes/forkhost/mod-master
[branch "mod-master"]
	remote = forkhost
	merge = refs/heads/mod-master
--8<---------------cut here---------------end--------------->8---

In order to keep an additional branch up to date with upstream, a
client need only fetch from the fork host, rebase on the upstream
branch, and then push (back to the fork host).  For example:

 $ cd mod-master

 $ git fetch --verbose forkhost
 […]
 4b3de748b0..f35648ba0c  master     -> master
 = [up to date]          mod-master -> forkhost/mod-master

 $ git status --short --branch
 ## mod-master...forkhost/mod-master

 $ git log --oneline ..master | wc -l
 1

 $ git rebase master
 Successfully rebased and updated refs/heads/mod-master.

 $ git status --short --branch
 ## mod-master...forkhost/mod-master [ahead 3, behind 2]

Note that, although there was only one ‘incoming’ commit, mod-master
and forkhost/mod-master have diverged (by 3 and 2 commits
respectively).  This is because there were (and are) two commits on
mod-master not found on master.

After fetching, C is the ‘incoming’ commit:

               master:  ---A---B---C

           mod-master:  ---A---B---X---Y

  forkhost/mod-master:  ---A---B---X---Y

After reabsing:

               master:  ---A---B---C

           mod-master:  ---A---B---C---X'---Y'

  forkhost/mod-master:  ---A---B---X---Y

As you can see, mod-master and forkhost/mod-master now diverge after B
(by 3 and 2 commits respectively).

This means that when the client tries to push, the updates are
rejected:

 $ git push
 To [forkhost]
  ! [rejected]              mod-master -> mod-master (non-fast-forward)
 error: failed to push some refs to [forkhost]
 hint: Updates were rejected because the tip of your current branch is behind
 hint: its remote counterpart. Integrate the remote changes (e.g.
 hint: 'git pull ...') before pushing again.
 hint: See the 'Note about fast-forwards' in 'git push --help' for details.

unless force is used:

 $ git push --force
 […]
  + cbb90030a9...5299b8bf0d mod-master -> mod-master (forced update)

It is obviously dangerous and wrong for a client to use force every
time it pushes to fork.git.  What if the client failed to notice
incoming commits on mod-master (as well as on master)?  Those missed,
incoming commits might be irretrievably lost.

So, how should one _safely_ publish a fork?

Sebastian