Search code examples
gitgit-fork

Is it possible to create a project to be the predecessor of existing projects? Some kind of pre-fork project?


I work in the front-end architecture department and we maintain several skeleton projects. In these, we define the basics that other teams use to start their own projects. We create some code to start a webpack devserver with babel; we set up the automated tests using testem, chai and mocha; we create scripts that help generating some configuration files. It all seems cool, but there is one flaw: the projects created from our skeletons don't fork from them, they clone the skeleton repo and then push their new project to another repo.

Every change to our skeleton projects will only affect future projects because the old projects don't get the changes automatically and they don't even do it manually (this is something we're currently trying to enforce).

So, it would be great to have a structure like this:

basic-skeleton
|--amp-skeleton
|  |--amp-project-1
|  |--amp-project-2
|--react-skeleton
   |--react-project-1

Is it possible to create this "fork relationship" after the projects have been created, so that we update the parent projects and they just have to merge these changes?


Solution

  • It all seems cool, but there is one flaw: the projects created from our skeletons don't fork from them, they clone the skeleton repo and then push their new project to another repo.

    But that is forking.

    Git is a distributed system. The commits keep their identity as they move between repositories. If they cloned your repository and pushed into theirs, the branches have common history, so they can pull from your repository and merge the baseline into their project anytime.

    The “fork” operation of GitHub, GitLab, BitBucket and similar is just a server-side clone. If they did the clone manually, the repository manager won't know it can do the merge, so they may have to do it manually too, but nothing is preventing it.


    That said, merging does not go well with overriding things. For a baseline and projects built on top of it it is usually better to have the baseline and customizations as some kind of layers combined by the build system, with the baseline checked out as sumbodule or downloaded by the build system as a dependency.


    Update:

    But I've learned that actually the new projects aren't created this way and thus they're not forks.

    There is still a way around it. It is often used by package maintainers (e.g. in Debian) to track modifications from upstream releases that are versioned in different, or non-public, version control system. It works by maintaining an ‘upstream’ branch:

    Initial import in the project (downstream) repository is a direct copy of some revision of the skeleton (upstream), and then changes are made on top of that. When it's time to merge with newer skeleton (upstream), a new branch (upstream) is created, newer version of the skeleton is copied in it and committed. This branch is then merged into the master to bring in the newer skeleton.

    Since the 3-way merge algorithm only cares about the current states and the most recent common ancestor and not the revisions in between, it does not matter you don't have that history. Just don't lose the upstream reference afterwards so you can update it next time you want to merge new skeleton.

    This workflow was used even before Git and was even more important there, because centralized systems can't replicate history between repositories like the distributed ones. It is even described in CVS manual as “vendor branches”. It is also explicitly one of the design use-cases for Git, and an important reason why Git guesses renames during merge rather than tracking file identity—because when importing tarred/zipped release into a vendor branch, you don't have the rename information.

    Note that you would significantly help this workflow by making explicitly numbered releases of the skeleton. It makes it easier to track of which version was imported and merged in each downstream project.