Skip to content

Instantly share code, notes, and snippets.

@ellisbrown
Forked from 0xjac/private_fork.md
Last active September 22, 2022 02:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ellisbrown/b880f46c25af40be0b0f0bd280ca7155 to your computer and use it in GitHub Desktop.
Save ellisbrown/b880f46c25af40be0b0f0bd280ca7155 to your computer and use it in GitHub Desktop.
Create a private fork of a public repository

Creating a Private Fork of a GitHub Repository

Basic Steps

  1. Create a new private repository on Github

  2. Fork the repo (BASE_REPO_URL) to your new private repo (PRIVATE_REPO_URL) as follows:

    BASE_REPO_URL=<BASE>  # remote URL of repo you are forking
    PRIVATE_REPO_URL=<PRIVATE>  # remote URL of your new private fork repo
    
    # Create a bare clone of the base repo
    git clone --bare $BASE_REPO_URL
    
    # Mirror-push your bare clone to your new private repo
    cd ${BASE_REPO_URL##*/}
    git push --mirror $PRIVATE_REPO_URL
    
    # Remove the temporary local repository you created in step 2
    cd ..
    rm -rf ${BASE_REPO_URL##*/}

Recommended Additional Steps

cd to your preferred workspace, then clone your private fork

git clone $PRIVATE_REPO_URL  # clone the private fork
cd ${${PRIVATE_REPO_URL##*/}%%.git}  # cd into the cloned directory

# add the original repo as remote to fetch (potential) future changes.
git remote add upstream $BASE_REPO_URL

# disable push on the remote (as you are not allowed to push to it anyway).
git remote set-url --push upstream DISABLE
  • You can list all your remotes with git remote -v. You should see:

    origin	<PRIVATE_REPO_URL> (fetch)
    origin	<PRIVATE_REPO_URL> (push)
    upstream	<BASE_REPO_URL> (fetch)
    upstream	DISABLE (push)
    
  • When you push, do so on origin with git push origin.

  • When you want to pull changes from upstream you can just fetch the remote and rebase on top of your work.

      git fetch upstream
      git rebase upstream/main

Additional information on creating a private fork by duplicating a repo is documented here.

see also: https://ellisbrown.github.io/programming/git-private-forks/#basic-steps

@lestephane
Copy link

lestephane commented Jul 7, 2022

You should give an example of <BASE> and <PRIVATE>, because it is not clear whether people can simply put <github-org>/<project> in there, or whether they have to put a full https clone URL.

@lestephane
Copy link

lestephane commented Jul 7, 2022

hmm git clone --bare $BASE_REPO clones to a directory with the .git extension, even when using a https://github.com URL, so the cd command will fail there regardless. git version 2.36.1 over here.

One would need to distinguish the two behaviours somehow

# Create a bare clone of the base repo
git clone --bare $BASE_REPO

# Mirror-push your bare clone to your new private repo, then cleanup
if [ -d ${BASE_REPO##*/}.git ]; then
  ln -s ${BASE_REPO##*/}.git .git;
  git push --mirror $PRIVATE_REPO
  rm -rf ${BASE_REPO##*/}.git .git
else
  pushd ${BASE_REPO##*/}  
  git push --mirror $PRIVATE_REPO
  popd
  rm -rf ${BASE_REPO##*/}
fi

@lestephane
Copy link

lestephane commented Jul 7, 2022

My version of git does not allow setting a remote url to DISABLE, I used DO.NOT.PUSH as suggested in https://stackoverflow.com/questions/7556155/git-set-up-a-fetch-only-remote#comment106237620_7556269

@ellisbrown
Copy link
Author

ellisbrown commented Aug 3, 2022

@lestephane thanks for the comments! Glad to see others have use for this.

You should give an example of <BASE> and <PRIVATE>, because it is not clear whether people can simply put <github-org>/<project> in there, or whether they have to put a full https clone URL.

Good suggestion---it was not clear. I updated the variable names and comments to hopefully make it clear that both should be remote URLs (SSH or HTTPS should work).

hmm git clone --bare $BASE_REPO clones to a directory with the .git extension, even when using a https://github.com URL, so the cd command will fail there regardless.

Yes, a directory ending in .git is expected when cloning with the --bare option. This should not be an issue though; you should be able to cd into it using cd ${BASE_REPO_URL##*/}.

The ##*/ is some bash variable mangling to extract everything after the last / in the URL.

Consider the following example:

$ foo=https://foo.com/bar/baz.git
$ echo ${foo##*/}
baz.git
$ cd ${foo##*/} # now in ./baz.git

git version 2.36.1 over here.

I've used this on a various computers with git >2.0. Just verified with 2.32.1.

@ellisbrown
Copy link
Author

Maybe an example would help as well. I'll demonstrate cloning my personal config repo into a tmp private repo from the /tmp/ dir on my computer:

/tmp $ BASE_REPO_URL=https://github.com/ellisbrown/config.git
/tmp $ PRIVATE_REPO_URL=https://github.com/ellisbrown/tmp.git

/tmp $ git clone --bare $BASE_REPO_URL

Cloning into bare repository 'config.git'...
remote: Enumerating objects: 76, done.
remote: Counting objects: 100% (76/76), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 76 (delta 29), reused 43 (delta 11), pack-reused 0
Receiving objects: 100% (76/76), 38.24 KiB | 1.82 MiB/s, done.
Resolving deltas: 100% (29/29), done.

/tmp $ cd ${BASE_REPO_URL##*/}

fatal: this operation must be run in a work tree

/tmp/config.git > main $ git push --mirror $PRIVATE_REPO_URL

Enumerating objects: 76, done.
Counting objects: 100% (76/76), done.
Delta compression using up to 8 threads
Compressing objects: 100% (45/45), done.
Writing objects: 100% (76/76), 38.24 KiB | 38.24 MiB/s, done.
Total 76 (delta 29), reused 76 (delta 29), pack-reused 0
remote: Resolving deltas: 100% (29/29), done.
To https://github.com/ellisbrown/tmp.git
 * [new branch]      main -> main
fatal: this operation must be run in a work tree

/tmp/config.git > main $ cd ..
/tmp $ rm -rf ${BASE_REPO_URL##*/}

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment