Search code examples
gitgit-pushgit-mirror

git-push combined with refspec from mirror repositories deletes other branches?


There are two remote repositories as follows.

  • original.git: The original bare repository.
  • mirror.git: A mirror repository cloned with git clone --mirror original.git.

Pushing refs from the mirror to the original remote using git push --mirror works as expected. However, when refspec (e.g. branch name) is combined with git-push, Git tries to delete all the other branches except for the specified one, from both original and mirror repositories.

  1. Why does Git try to delete other branches?
  2. How can I prevent from deleting remote branches in cases where receive.denyDeletes is not set in remote repositories? (I just deleted remote branches by mistake.)

Note: I'm using git v2.18.0 now, and as far as I know, git push --mirror <repo> <refsepc> is not allowed in older versions such as git v1.7.1.

bash-4.1$ cd mirror.git/
bash-4.1$ git branch
* master
  new_branch

bash-4.1$ git config --list | grep remote
remote.origin.url=/user/han/git/original.git/
remote.origin.fetch=+refs/*:refs/*
remote.origin.mirror=true

bash-4.1$ git push --mirror
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 16 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 280 bytes | 280.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /user/han/git/original.git/
 * [new branch]      new_branch -> new_branch

bash-4.1$ git push origin master
To /user/han/git/original.git/
 - [deleted]         new_branch

Solution

  • The specification for the --mirror option in git push is:

    Instead of naming each ref to push, specifies that all refs under refs/ (which includes but is not limited to refs/heads/, refs/remotes/, and refs/tags/) be mirrored to the remote repository. Newly created local refs will be pushed to the remote end, locally updated refs will be force updated on the remote end, and deleted refs will be removed from the remote end. This is the default if the configuration option remote.<remote>.mirror is set.

    (boldface mine). Combining --mirror with a command-line refspec causes your Git to believe that all references not mentioned on the command line are deleted, which causes your Git to send "delete this ref" requests1 to the other Git.

    One could argue that this is a bug—that your own Git should simply reject the attempt to combine --mirror with command-line-argument refspecs—and it's certainly a bit unkind. See also --prune, which has similar behavior and is meant to be combined with command-line refspecs.


    1As usual, these take the form of polite "please, if you're willing, do <thing>" unless you add the --force flag to your command line, or a plus sign to the refspec that generates the request. Unfortunately, polite requests to delete a branch are by default obeyed, unlike polite requests to move a branch in a fashion that is not a fast-forward.