How to keep public and private versions of a git repo in sync?

The simplest solution would be to have private branch in your 'private' repository, i.e. branch which is simply not pushed to public repository.

For that you would probably need to either specify all branches you want to push to public repository in config (instead of using globbing mirroring refspec +refs/*:refs/*), or push carefully branches you want to publish and rely on Git behaviour of pushing matching branches (those which are present on remote side) setting "push.default" to current default value "matching".

If you by accident push your private branch, you can delete it in remote repository (unless it is configured to forbid this) using "git push <remote> :refs/heads/<private-branch>" (to remember this: push empty value to remote branch). You can protect against accidental pushing of your private branch using hooks on remote side, see e.g. update-paranoid example hook in contrib/examples.


Sidenote: Junio C Hamano, git maintainer, pushes to public git repositories only specified set of branches: 'maint', 'master', 'next', 'pu' (proposed updates), and 'html', 'man', 'todo'; he does not publish short-lived often changing feature branches.



EXAMPLE SETUP:

    working repository  ---->  private repository (bare)  ---->  public repository  
    \------ private -------/     \------- protected ------------/     \------- public -----/

"Working repository" is the repository with working area that you make commits in, where you pull changes and resolve conflicts; repository where you do your work. Let's say it contains the following branches: 'public' with changes that can be published to the world, 'private' that you want to not share with others or share only with selected subset of people, and perhaps some feature branches like 'ticket-234' or 'add-frobnicator', which are not ready even for selected group to see. This repository is non-bare, as it not published.

It would have something like the following configuration for pushing to 'private' repository. Note that "matching branches" behavior is set explicitely here, see git-pull manpage:

[remote "private"]
        url = [email protected]:/srv/private/git/repo.git
        push = +:

"Private repository" is public bare repository available only to selected people, for example it is available for fetching only via SSH. It has only branches which are ready, i.e. 'public' and 'private' branches; either those branches were present when creating "private" repository, or were pushed explicitely ("git push private <branch>") from "working" repository. Push from "working" repository pushes (transfers) only matching branches, i.e. only 'public' and 'private' branches.

"Private repository" has post-update or post-receive hook set which pushes 'public' branch only to "public repository" (see below) if it was pushed to it (well, it can push unconditionally).

"Public repository" is public bare repository available to everyone, for example it is hosted on GitHub, and/or Gitorious, and/or repo.or.cz, or perhaps it is served via git:// protocol using git-daemon. It contains only 'public' branch, and it uses update or pre-receive either to accept whilelist of branches (here only 'public' branch is accepted), or reject blacklist of branches (in this example pushes to/creating 'private' branch would be rejected). It is updated automatically when pushing to "private" repository.

This setup might be overly complicated for your needs; "private" repository might be not necessary in your case. In such case the configuration in "working repository" for pushing directly to "public repository" would look like this:

[repository "public"]
        url = ssh://example.com/srv/git/repo.git
        push = refs/heads/public:refs/heads/public

I hope this example would help; please however read the documentation and do not use it blindly.


If the code regarding the "part of a pay service" is separate (i.e. "in another directory") than the code representing the "open-source Rails app" pushed to public repository, you could:

  • defined the public part as an independent repository (that you can push whenever you need to)
  • defined the private part as another independent repository
  • use submodules to include your public part within your private repository

That way, you are working with the all system (public Rails app + private pay system), but only push the public part to GitHub, and you can push the all system (private + public) to another private repository (on a backup machine for instance).


If the private code is mixed with the public one... then see Talljoe's answer or Jakub Narębski's answer.