阻止一个git分支被推送

54

情况如下:

我在github.com上有一个公共仓库,用于我的开源应用程序。然而,现在我想编写一些特定的代码,这些代码将不会公开(我可能会在商业版应用程序中使用它)。

我想使用同一个仓库,在我的git仓库中创建一个"私有"分支,我不会进行推送。

但是,错误难免会发生。有没有办法禁止git将分支推送到远程服务器上?

如果有更好的处理这种情况的方法,我当然欢迎任何建议。

7个回答

65
这里介绍的是pre-push钩子方法,主要用于与名为dontpushthis的分支一起使用。请按照以下步骤创建.git/hooks/pre-push文件:
#!/usr/bin/bash
if [[ `grep 'dontpushthis'` ]]; then 
  echo "You really don't want to push this branch. Aborting."
  exit 1
fi

这能生效是因为推送的参考列表被传递到标准输入流中。所以这也会捕获 git push --all

将其设为可执行文件。

在每个本地仓库中操作此步骤。

当你尝试将其推送到该分支时,你将看到:

$ git checkout dontpushthis
$ git push
You really don't want to push this branch. Aborting.
error: failed to push some refs to 'https://github.com/stevage/test.git'

显然,这很简单,只是防止推送名为“dontpushthis”的分支。因此,如果您想避免直接推送到重要分支(如master),则非常有用。
如果您试图解决防止机密信息泄露的问题,可能不足够。例如,如果您从dontpushthis创建了一个子分支,则无法检测到该分支。您需要更复杂的检测 - 您可以查看当前分支上是否存在“dontpushthis”分支上的任何提交,例如。
更安全的解决方案
再次查看问题,我认为在这种情况下更好的解决方案是:
1. 有一个公共的存储库 2. 将其克隆到一个私有的工作目录中 3. 从该工作目录中删除远程(git remote rm origin) 4. 要合并公共更改,只需执行git pull https://github.com/myproj/mypublicrepo 这样,私有存储库工作目录永远没有可以推送的地方。实际上,您拥有一个公共信息到私有信息的单向阀门,但不能返回。

2
这是我正在使用的代码:#!/bin/sh if [[ `grep -e develop -e master` ]]; then echo "请不要直接推送到develop或master分支" exit 1 fi - Jacob Evans
2
更安全的解决方案更好。谢谢! - Julio Saito
8
我更喜欢更安全的解决方案!与其使用第3种方法,我会选择执行“git remote set-url --push origin http://no-push-to-this-remote:99999/”命令。这样只会将该远程仓库的推送URL设置为无效URL(端口号99.999是无效的),因此可以进行常规的获取/拉取操作,但将文件推送至该远程仓库时将立即失败并显示错误消息。这比每次都要提供获取URL更加方便。 - Stefan

29

一个有点hack的解决方案:在GitHub上创建一个与你的真正分支同名的虚拟分支,并确保它不会是快进合并(fast forward merge)。这样,推送操作将失败。

以下是一个例子。

$ git clone git@github.com:user/repo.git
$ cd repo
$ git checkout -b secret
$ echo "This is just a dummy to prevent fast-forward merges" > dummy.txt
$ git add .
$ git commit -m "Dummy"
$ git push origin secret

现在虚拟分支已经设置好了,我们可以在本地重新创建它以与GitHub上的分支产生分歧。

$ git checkout master
$ git branch -D secret
$ git checkout -b secret
$ echo "This diverges from the GitHub branch" > new-stuff.txt
$ git add .
$ git commit -m "New stuff"

现在,如果我们不小心尝试进行push操作,它将会因为无法进行快速向前合并而失败:

$ git push origin secret
To git@github.com:user/repo.git
! [rejected]        secret -> secret (non-fast forward)
error: failed to push some refs to ‘git@github.com:user/repo.git

1
抱歉,您能否解释一下“确保它不是快进式合并”的意思?谢谢。 - houbysoft
2
在您的虚拟分支上提交一个不会出现在真实分支上的提交,以使它们分叉。 - hammar
不错!如果你既有一个公共的(origin)仓库,也有一个私有的(private)仓库,这个方法就可以奏效。只需将虚拟分支放在公共仓库上。这样,git push private secret 就能正常工作,而 git push origin secret 则不能。 - Benjamin Atkin
1
一种不那么hackish的方法是编写一个pre-push钩子并将其包含在本地存储库中,但该脚本需要放置在每个具有分支访问权限和推送能力的存储库中。 - Dan Hunsaker
3
在我看来,pre-push 更好。这种解决方案对于像我这样喜欢使用 'git push -f' 的人不适用。我有一个公共的小仓库分享我的点文件。因此,'git push -f' 并不是什么大问题(我会进行变基/重置等操作,以使历史记录干净整洁 :( )。但我真的需要确保包含机密信息的私有分支永远不会被推送到远程。 - Lungang Fang
1
对于GitHub来说,更加简单的方法是在GitHub上创建分支并启用分支保护,要求进行拉取请求审查或状态检查(确保包括管理员)。然后GitHub将不允许您推送该分支,即使使用“-f”也不行。 - asmeurer

10
一个对.git/hooks/pre-push脚本的修补,来自@steve-bennett。
#!/usr/bin/bash

branch_blocked=mine

if grep -q "$branch_blocked"; then
    echo "Branch '$branch_blocked' is blocked by yourself." >&2
    exit 1
fi

6

为什么不直接使用当前git版本提供的pre-push示例呢?

这个想法是在你的私有分支的第一次提交的提交消息中使用单词PRIVATE:

设置pre-push脚本后,每次推送时,它会检查所有已推送ref的日志的提交消息。如果它们以PRIVATE:开头,则会阻止推送。

以下是步骤:

  • Create a file in .git/hooks/pre-push
  • Give it execution rights
  • Past the following script in it

    #!/bin/sh
    
    remote="$1"
    url="$2"
    
    z40=0000000000000000000000000000000000000000
    
    IFS=' '
    while read local_ref local_sha remote_ref remote_sha
    do
            if [ "$local_sha" = $z40 ]
            then
                    # Handle delete
                    :
            else
                    if [ "$remote_sha" = $z40 ]
                    then
                            # New branch, examine all commits
                            range="$local_sha"
                    else
                            # Update to existing branch, examine new commits
                            range="$remote_sha..$local_sha"
                    fi
    
                    # Check for WIP commit
                    commit=`git rev-list -n 1 --grep '^PRIVATE:' "$range"`
                    if [ -n "$commit" ]
                    then
                            echo "Error: Found PRIVATE commit in $local_ref."
                            echo "The commit is in the range $range."
                            echo "NOT pushing!"
                            exit 1
                    fi
            fi
    done
    
    exit 0
    
    remote="$1"
    url="$2"
    

失败示例

$ git push origin private/old-kalman-filter 
Found PRIVATE commit in refs/heads/myforbiddenbranch, the commit is in the range 
a15c7948676af80c95b96430e4240d53ff783455. NOT PUSHING!
error: failed to push some refs to 'remote:/path/to/remote/repo'

如果要使分支能够再次推送,可以删除挂钩或者更改提交消息以删除被禁止的词。

通过检查remote_ref,可以修改此脚本以仅考虑一个被禁止的远程。但在这种情况下,请不要忘记将此挂钩复制到允许接收此分支的所有存储库中。


1
这非常简单。我还添加了一个检查远程名称的步骤,因此如果远程名称以字符串“private”开头,则推送将继续进行而无需进一步检查。通过这种方式,我可以拥有一个私有远程(例如用于备份),在其中可以推送所有内容,以及一个公共远程,其中包含常见内容但没有私人部分。谢谢! - polettix

2
如果您使用GitHub,可以在GitHub上创建与您的分支名称相同的分支。无需向其中推送任何提交,只需从主分支或其他分支创建一个空分支(您可以在GitHub界面中通过在“分支”下拉菜单中键入分支名称并单击“创建分支<分支名称>”来完成此操作)。
然后,转到存储库的分支设置(例如,https://github.com///settings/branches/),为您的分支启用分支保护。确保勾选所有框,特别是要求审核或状态检查框,这将禁止直接推送该分支(必须通过拉取请求推送),并确保勾选包括管理员的框。
然后GitHub不允许您向分支推送,即使您使用-f选项。您会收到类似以下内容的消息:
$ git push -f origin private
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 981 bytes | 981.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
remote: error: GH006: Protected branch update failed for refs/heads/private.
remote: error: At least 1 approving review is required by reviewers with write access.
To github.com:<user>/<repo>.git
 ! [remote rejected] private -> private (protected branch hook declined)
error: failed to push some refs to 'git@github.com:<user>/<repo>.git'

2

有多种解决方案:

  1. 非技术性解决方案,只需将许可证调整为商业许可证即可
  2. 在GitHub上创建包含您的分支的私有存储库
  3. 在服务器上设置Git挂钩(据我所知,无法在GitHub上进行此操作)
  4. 编写git-push的封装器以防止使用git push提交更改

3
还有一些客户端钩子可以使用,例如在这种情况下,“pre-push”是您的候选项。 - Dan Hunsaker

2
您可以创建一个在远程仓库中不存在的分支。
这样,如果您只需执行以下操作:
git push origin

它只会推送在远程仓库上存在的分支。

创建分支后,请查看本地仓库目录中的.git/config文件 - 您将看到每个本地分支都可以分配不同的远程仓库。您可以通过将此分支分配给单独的(私有)仓库来利用它,但这并非普遍解决方案(如果明确命令或使用git push origin命令,则仍然可以将分支推送到源远程)。


@manojlds:从技术上讲,确实只有两个if语句;) 说真的,请解释一下你的意思。 - Tadeck
通常我和许多人都会使用git push remote branch的方式,这会使你所提到的两个if条件无效。 - manojlds
如果您命令git将您的私有分支上传到您的私有存储库(git push origin my_private_branch),为什么您认为git不应该这样做?实际上,我认识的大多数人都只使用git push(或git push [remote]),如果有多个分支、远程和编写git push origin my_private_branch似乎要写100次,这样效率会低得多。 - Tadeck
你为什么认为 Git 不应该这样做?这就是这个问题的重点。你在第二部分可能是正确的。 - manojlds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接