- 原文地址:-force considered harmful; understanding git's --force-with-lease
- 原文做者:Steve Smith
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:LeviDing
- 校对者:yifili09
Git 的 push --force
具备破坏性,由于它无条件地覆盖远程存储库,不管你在本地拥有什么。使用这个命令,可能覆盖团队成员在此期间推送的全部更改。然而,有一个更好的办法,当你须要强制推送,但仍需确保不覆盖其余人的工做时,-force-with-lease
这条指令选项能够帮助到你。前端
众所周知,git 的 push -force
指令是不推荐被使用的,由于它会破坏其余已经提交到共享库的内容。虽然这不老是彻底致命的(若是那些修改的内容仍在某些同事的本地工做域中,那以后他们能被从新合并),可是这样的作法很欠考虑,最糟糕的状况会形成灾难性的损失。这是由于 --force
指令选项迫使分支的头指针指向你我的的修改记录,而忽略了那些其余和你同时进行地更改。react
强制推进最多见的缘由之一是当咱们被迫 rebase
一个分支的时候。为了说明这一点,咱们来看一个例子。咱们有一个项目,其中有一个功能分支,Alice 和 Bob 要同时在这个分支上工做。他们都 git clone...
了这个仓库,并开始工做。android
最初,Alice 完成了她负责的功能,并将其 push
到主仓库。这都没啥问题。ios
Bob 也完成了他的工做,但在 push
以前,他注意到一些变化已被合并到了 master 分支。想要保持一棵整洁的工做树,他会对主分支执行一个 rebase
。固然,当他 push
这个通过 rebase
的分支的时候将被拒绝。然而,Bob 没有意识到 Alice 已经 push
了她的工做。Bob 执行了 push --force
命令。不幸的是,这将清除 Alice 在远程主仓库的全部更改和记录。git
这里的问题是,进行强制推送的 Bob 不知道为何他的 push
会被拒绝,因此他认为这是 rebase
形成的,而不是因为 Alice 的变化。这就是为何 --force
在同一个分支上协做的时候要杜绝的;而且经过远程主仓库的工做流程,任何分支均可以被共享。github
可是 --force
有一个不为众人所知的亲戚,它在必定程度上能防止强制更新操做带来的结构性破坏;它就是 --force-with-lease
。后端
--force-with-lease
是用于拒绝更新一个分支,除非该分支达到咱们指望的状态。即没有人在上游更新分支内容。 实际上,经过检查上游引用是咱们所指望的,由于引用是散列,并将父系链隐含地编码成它们的值。安全
你能够告诉 --force-with-lease
究竟要检查什么,默认状况下会检查当前的远程引用。这在实践中意味着,当 Alice 更新她的分支并将其推送到远程仓库时,分支的引用指针将被更新。如今,除非 Bob从远程仓库 pull
一下,不然本地对远程仓库的引用将过时。当他使用 --force-with-lease
推送时,git 会检查本地与远程的引用是否对应,并拒绝 Bob 的强制推送。--force-with-lease
有效地只在没有人在上游更新分支内容的时候容许你强制推送。就像是一个带有安全带的 --force
。它的一个快速演示可能有助于说明这一点:bash
Alice 已经对该分支进行了一些更改,并已推送到了远程主仓库。Bob 如今又对远程仓库的 master
分支进行了 rebases
操做:服务器
ssmith$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Dev commit #1
Applying: Dev commit #2
Applying: Dev commit #3复制代码
rebase
以后,他试图将本身的更改 push
上去,但服务器拒绝了,由于这会覆盖 Alice 的工做:
ssmith$ git push
To /tmp/repo
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to '/tmp/repo'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.复制代码
但 Bob 认为这是 rebase
操做形成的,并决定强制 push
:
ssmith$ git push --force
To /tmp/repo
+ f82f59e...c27aff1 dev -> dev (forced update)复制代码
然而,若是他使用了 --force-with-lease
,则会获得不一样的结果,由于 git 会检查远程分支,发现 从上一次 Bob 使用 fetch
到如今,实际上并无被更新:
ssmith$ git push -n --force-with-lease
To /tmp/repo
! [rejected] dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'复制代码
固然,在这有一些关于 git 的注意事项。上面展现的,只有当 Alice 已经将其更改推送到远程存储库时,它才有效。这不是一个严重的问题,可是若是她想修改她提交的东西,那她去 pull
分支时,会被提示合并被更改。
一个更微妙的问题是,咱们有方法去骗 git,让 git 认为这个分支没有被修改。在正常使用状况下,最常发生这种现象的状况是,Bob 使用 git fetch
而不是
git pull`来更新他的本地副本。
fetch将从远程仓库拉出对象和引用,但没有匹配的
merge则不会更新工做树。这将使本地仓库看起来已经与远程仓库进行了同步更新,但实际上本地仓库并无进行更新,并欺骗
--force-with-lease` 命令,成功覆盖远程分支,就像下面这个例子:
ssmith$ git push --force-with-lease
To /tmp/repo
! [rejected] dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'
ssmith$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/repo
1a3a03f..d7cda55 dev -> origin/dev
ssmith$ git push --force-with-lease
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 845 bytes | 0 bytes/s, done.
Total 9 (delta 0), reused 0 (delta 0)
To /tmp/repo
d7cda55..b57fc84 dev -> dev复制代码
这个问题的最简单的答案就是,简单的说“不要在没有合并的状况下 fetch
远程该分支”(或者更经常使用的方法是 pull
,这个操做包含了前面的两个),可是若是因为某种缘由你但愿在用 --force-with-lease
进行代码上传以前进行 fetch
,那么这有一种比较安全的方法。像 git 那么多的属性同样,引用只是对象的指针,因此咱们能够建立咱们本身的引用。在这种状况下,咱们能够在进行 fetch
以前,为远程仓库引用建立“保存点”的副本。而后,咱们能够告诉 --force-with-lease
将此做为引用值,而不是已经更新的远程引用。
为了作到这一点,咱们使用 git 的 update-ref
功能来建立一个新的引用,以保存远程仓库在任何 rebase
或 fetch
操做前的状态。这有效地标记了咱们开始强制 push
到远程的工做节点。在这里,咱们将远程分支 dev
的状态保存到一个名为 dev-pre-rebase
的新引用中:
ssmith$ git update-ref refs/dev-pre-rebase refs/remotes/origin/dev复制代码
这时呢,咱们就能够进行 rebase
和 fetch
操做,而后使用保存的 ref
来保护远程仓库,以防有人在工做时作了更改:
ssmith$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Dev commit #1
Applying: Dev commit #2
Applying: Dev commit #3
ssmith$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/repo
2203121..a9a35b3 dev -> origin/dev
ssmith$ git push --force-with-lease=dev:refs/dev-pre-rebase
To /tmp/repo
! [rejected] dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'复制代码
咱们能够看到 --force-with-lease
对于有时须要进行强制推送的 git 用户来讲,是一个颇有用的工具。可是,对于 --force
操做的全部风险来讲,这并非万能的,若是不了解它内部的工做及其注意事项,就不该该使用它。
可是,在最多见的用例中,开发人员只要按照正常的方式进行 pull
和 push
操做便可。偶尔使用下 rebase
,这个命令提供了一些咱们很是须要的,防止强制推送带来破坏的保护功能。所以,我但愿在将来版本的 git(但可能 3.0 之前都不会实现),它将成为 --force
的默认行为,而且当前的行为将被降级到显示其实际行为的选项中,例如:--force-replace-remote
。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。