默认启用 push --force-with-lease

175
我刚刚了解到git push --force-with-lease这个命令,它非常棒。但是,我并不经常使用强制推送,所以我担心下次需要用到这个好用的功能时会忘记。
有没有一种方法可以配置git,让git push -f自动使用--force-with-lease,除非我故意用--no-force-with-lease来覆盖它?
(我无法想象什么情况下会想要使用不带租约的force!)
6个回答

180

目前还没有办法在git中配置默认使用force-with-lease而不是force,因此最好的选择是创建一个别名来实现这个目的。

编辑 这仍然适用于2022年5月。但是git 2.30添加了一个额外的选项force-if-includes,使force-with-lease更加安全;如果想了解原因,请查看这个详细答案。

创建一个别名

要创建一个别名,可以使用git config --global alias.<alias-name> <command>命令,对于我们的情况,我建议类似于以下内容。

git config --global alias.pushfwl "push --force-with-lease"

这将在您的全局 .gitconfig 文件中创建一个条目(通常可以在您的主目录中找到)。之后,您可以使用git pushfwl来强制推送。

动手实践吧

或者,您可以决定自己实现此功能!如果您不确定从何处开始,可以查看 git 存储库中的文档目录。在这里,您可以找到编码指南和有关如何提交补丁的信息。

您可以在官方社区页面上找到所有这些链接以及更多链接。


33
关于这不是一个功能的说明:反对重写标准命令(如“push --force”)的常见论点是,你习惯使用它们,忘记它们的起源,并且有一天在新系统上意外地以那种方式使用它们。就像将rm别名为.bashrc中的rm -i一样;你会忘记,在服务器上删除一个重要文件。采用自己的别名就不存在这个问题 :) - hraban
7
个人轶事/警告:我尝试将其别名为 pushf,但总是仔细检查自己是否没有执行 push -f,因为它看起来很像这个别名。一些团队成员仍然使用 push -f,认为别名只是一个化妆品上的缩写。最终,我们将更安全的形式别名为 pushfl,并停止担心它。 - kelvin

49

我的解决方案是创建一个包装脚本,并使用别名以便我始终在实际的git位置使用它。

每当我尝试git push -f时,我会看到以下内容:

⚡ git push -f
use this instead so you don't cause race conditions in the 
repo: git push --force-with-lease

这个脚本的一些优点是:
  • 它训练我习惯使用--force-with-lease,所以当我做错时不会被批评
  • 如果出于某种原因,我们确实需要强制推送,git push --force将起作用。
如何实现:
  1. 创建一个自定义脚本,将任何参数传递给git,除了-f
  2. 别名该脚本,以便我们使用它而不是git
这些说明假设您正在运行bash的Linux或Mac。我没有尝试过zsh或Windows,但我认为它们也可以工作。 ~/.bash_profile:
alias git=~/.git_wrapper.sh

~./git_wrapper.sh:

#!/bin/bash
for arg in "$@"; do
    if [ "$arg" = "push" ]; then
        ispush=1
    elif [ "$ispush" = 1 -a "$arg" = '-f' ]; then
        echo "use this instead so you don't cause race conflicts in the repo: git push --force-with-lease"
        exit 1
    fi
done

git "$@"

进行这些更改后,重新启动终端,git 现在应该会在您尝试强制推送时变得挑剔。


22
那看起来很方便。+1。也许可以把“嘿,白痴”换成“嘿,你温柔但单纯的灵魂”之类的话 ;) - VonC
永远不应该有必要使用git push --force,因为只要在进行git push --force-with-lease之前执行git fetch,就可以始终成功。 - Ragas

37
2020年:我在“ git push --force-with-lease 是否总是安全的?”中提到了这个新选项。
git push --force-if-includes

Add a check to verify if the remote-tracking ref of the local branch is reachable from one of its "reflog" entries.

Alexey Romanov评论中提到:
"何时应该使用"git push --force-if-includes",其中添加了更多细节。


2017年:
我担心下次需要这个方便的功能时会忘记它。
Git 2.13(2017年第二季度)解释了为什么没有对这个推送选项的“保护”,因为即使你在git push级别上不忘记它,它可能仍然被忽略。
查看提交 f17d642(2017年4月19日)由Ævar Arnfjörð Bjarmason(avar完成。
(由Junio C Hamano -- gitster --提交 46bdfa3中合并,2017年4月26日)

push:使用多个远程仓库文档和测试--force-with-lease

文档和测试用例,其中存在两个指向相同URL的远程仓库,并且后台获取和随后的git push --force-with-lease不应该覆盖我们尚未获取的未更新引用。
一些编辑器(如微软的VSC)具有自动后台获取的功能,这将绕过--force-with-lease--force-with-lease=<refname>提供的保护措施,正如此处添加的文档中所述。
因此,git push的文档现在包括:

general note on safety: supplying this option without an expected value, i.e. as --force-with-lease or --force-with-lease=<refname> interacts very badly with anything that implicitly runs git fetch on the remote to be pushed to in the background, e.g. git fetch origin on your repository in a cronjob.

The protection it offers over --force is ensuring that subsequent changes your work wasn't based on aren't clobbered, but this is trivially defeated if some background process is updating refs in the background. We don't have anything except the remote tracking info to go by as a heuristic for refs you're expected to have seen & are willing to clobber.

If your editor or some other system is running git fetch in the background for you, a way to mitigate this is to simply set up another remote:

git remote add origin-push $(git config remote.origin.url)
git fetch origin-push

Now when the background process runs git fetch origin the references on origin-push won't be updated, and thus commands like:

git push --force-with-lease origin-push

Will fail unless you manually run git fetch origin-push.
This method is of course entirely defeated by something that runs git fetch --all, in that case you'd need to either disable it or do something more tedious like:

git fetch              # update 'master' from remote
git tag base master    # mark our base point
git rebase -i master   # rewrite some commits
git push --force-with-lease=master:base master:master

I.e. create a base tag for versions of the upstream code that you've seen and are willing to overwrite, then rewrite history, and finally force push changes to master if the remote version is still at base, regardless of what your local remotes/origin/master has been updated to in the background.


如果有其他人遇到此答案:自 Git 2.30 起,有--force-if-includes来解决--force-with-lease这个问题。参考链接:https://dev59.com/DVEG5IYBdhLWcg3wcuD2 - Alexey Romanov
@AlexeyRomanov 谢谢您的反馈。我(终于)成功将您的评论包含在答案中,以增加可见度。 - VonC

7

我想提醒自己不要使用-f选项,但又不想被骗认为-f意味着--force-with-lease。因此,我的建议是:

git() {
  if [[ $@ == 'push -f'* || $@ == 'push --force '*  ]]; then
    echo Hey stupid, use --force-with-lease instead
  else
    command git "$@"
  fi
}

将以下内容添加到您的.bash_profile.bashrc.zshrc中。

6
你可以创建一个Bash函数来替换git,并使用--force-with-lease代替--force
# replaces `git push --force` with `git push --force-with-lease`
git() {
  if [[ $@ == 'push -f'* || $@ == 'push --force'* ]]; then
    command git push --force-with-lease
  else
    command git "$@"
  fi
}

或者,用一行代码实现:

git() { if [[ $@ == 'push -f'* || $@ == 'push --force'* ]]; then command git push --force-with-lease; else command git "$@"; fi; }

将其添加到您的~/.bashrc~/.zshrc中。

6
这个很危险:它将删除所有git push -f的参数。 - neu242
2
可以想象一种更安全的版本,它通过迭代参数来交换“-f”或“--force”为“--force-with-lease”,并传播所有其他参数。 - Jonathan Hartley
我成功地制作了一个版本,可以在任何地方替换-f | --force参数并保留其他参数(fn() { if [[ $1 == 'push' ]]; then command echo fn "${@/(-f|--force)/--force-with-lease}"; else command echo fn "$@"; fi; }),但我无法将其替换为整个单词,因此它会将--follow-tags替换为---force-with-leaseollow-tags。有什么建议吗? - paulodiovani

6
如果您使用OMYZSH,您可以简单地使用ggfl命令。

请确保在 ~/.zshrc 中启用了 git 插件,否则会出现 gpf - JamesWilson

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