使用 Git 是否等同于 Mercurial Push 命令?

Using Git is there equivalent to Mercurial Push command?

我已经使用 TortoiseHg/Mercurial 很多年了,现在我第一次使用 Git SourceTreeTortoiseGit GUI。从这个 link 我了解到 PUSHGit 上是不同的水星Mercurial PUSH 只影响远程仓库,不影响工作空间。另一方面,PUSH 和 Git 不仅会更新存储库,还会更新工作区(以及 Mercurial 没有的索引)。

我的问题是 Git 是否具有 Mercurial PUSH 的等价物?我可以 PUSH 到远程存储库并保持工作区和索引不变吗?我知道 Git FETCH 相当于 Mercurial PULL 因为它们都没有修改本地工作区。实际上,我正在为 Git 寻找反向 FETCH。这可能还是我必须始终打开远程存储库并改为执行 FETCH

Git推送不修改索引

编辑:TL;DR 摘要:

  • 当使用 Git 时,您 可以 推送到人类使用的存储库,它有一个工作树。您只是 不应该 推送到该存储库中的 current 分支,至少,不是没有特殊考虑。所以 Git 默认阻止它。

  • Mercurial 允许您推送到这样的存储库,包括 "current branch"——这个短语在 Mercurial 中有不同的意思——因为在 Mercurial 中,Git 会调用 "detached HEAD"是正常可用的状态。这个可以在Git中完成,就是不太实用

  • 因此,Git 提供,您通常应该使用 bare 存储库作为推送目标。它们没有工作树,因此 Git 允许推送它们,甚至推送到 "current branch"(Git 的概念,而不是 Mercurial 的)。

下方为完整回复原文。


作为,这个说法是错误的:

PUSH with Git on the other hand not only updates the repository but also the workspace (and index which Mercurial doesn't have).

虽然推送更新存储库(像往常一样添加新提交),但它不会——至少在默认情况下不会——更新索引,也不会更新工作树。 (从 Git 2.4.0 开始的 Git 版本可以配置为这样做。)

这里的问题是一些相当深刻的哲学概念之间的不匹配。这个问题的答案:

My question is does Git have the equivalent of Mercurial PUSH? Can I PUSH to a remote repository and leave the workspace and index as it is?

是 "yes, using detached HEAD",但这个答案并不像您想象的那么有用。

在 Mercurial 中,当前修订版(称为 .,与 Git 中的名称 HEAD 相比)被检出,并可能被修改,其方式与如果您使用的是 Git "detached HEAD",它将在 Git 中(尽管有一些重要的区别)。索引本身——在 Git 中存在和暴露的东西,但不以那种形式存在并且在 Mercurial 中隐藏得很好——没有直接相关。

问题在于,在 Mercurial 中,当前的 分支 与当前的 修订版 完全分开。在Git中,两者是deeply intertwingled。显然,当前修订版在某个分支上:all 修订版在某个分支上,在 Mercurial 中。但在 Mercurial 中,所有修订都在 恰好一个分支 上。在 Git 中,一次修订——一次提交——可能同时在 多个分支上 :none,一个、两个、十个或数千个。虽然提交是不变且永久的(在 Git 和 Mercurial 中),但 分支集 包含 提交是固定的并且在 Mercurial 中不变,但在 Git.

中完全流畅

Git(故意1)摒弃了分支 name 不仅仅是一个临时的、短暂的标签的想法.使用分支名称只是为了识别 一个特定的提交 ,现在,主要供人类使用,但也供 git pushgit fetch 期间使用.

因此,由于 Git 中的 HEAD 包含 分支名称 — 我们将此称为 cb 表示当前分支 — 而 branch name 包含提交 ID,如果我们以意想不到的方式更改 name-to-ID 映射,我们将遇到可怕的麻烦。如果收到推送更新cb,而我们(通过HEAD使用它作为我们的当前版本,我们会感到困惑:我们会相信我们的索引和工作树适用于 cb now 指向的提交,而不是 cb 用于更新前指向

同样,这在 Mercurial 中不是问题,因为在 Mercurial 中,系统知道哪个是当前版本。将新的修订(提交)推送到分支中不会影响我们的 当前 修订,事实上,如果我们在不更新的情况下进行新提交,我们只会得到一个新的提交,即新的头(小写,不是 Git 的全大写 HEAD 东西)。这个新头像将有 no 名字,除非我们使用书签,但是没有名字的头像是 Mercurial 中的普通状态。我们只是 运行 hg heads 找到这些未命名的 heads,并且 hg update -r 对那些修改进行处理(例如将它们合并,通常与其他 heads 合并)。

在Git中,你可以得到的正是这个状态:就是那个"detached HEAD"。分离的 HEAD 直接包含提交 ID,而不是分支名称。现在 Git 不会 丢失 当前修订版(或者更确切地说,提交哈希 ID),可以安全地推送到此存储库。

问题是,当你有一个分离的 HEAD 时,虽然你 可以 进行新的提交,但它们 只有 名字 HEAD。他们在没有个分支。 (如果您愿意,可以将 HEAD 称为 "anonymous branch"。它的名称为 HEADHEAD 实际上并不是 分支 名称,因为所有分支名称半秘密地以 refs/heads/ 开头,而 HEAD 不是。)为了让 Git 更永久地记住它,你必须给这个东西一个分支名称:

git checkout -b newbranch

HEAD 从 "detached" 更改为 "attached",再到新分支 newbranch。分支名称 newbranch 现在存储提交哈希 ID,名称 HEAD 现在存储分支名称 newbranch.

但是现在你不能推送到 newbranch!

所以只要 HEAD 不是 "detached",有些分支你就不能,或者至少不应该推送到 ... 并且 HEAD 可以正常工作,它最终必须重新附加到一个分支,可能是 new 分支。

请注意,您可以推送到任何其他分支。所有 other 分支在索引中都是 not 并且在工作树中是 not

Git 2.4.0 及更高版本中的新功能是您可以将 receive.denyCurrentBranch 设置为 updateInstead。当这样设置时,git push的接收端的一个Git检查:

  • 推送到的是当前分支的分支吗?即HEAD里面有没有分支的名称,如果有,push是不是要更新这个分支名称?
  • 如果没有(并且所有其他测试都通过),允许更新。
  • 如果是,请检查 receive.denyCurrentBranch
    • truerefuse:拒绝推送
    • falseignore:接受推送,单独留下索引和工作树,依赖于使用此存储库的人知道该做什么
    • warn:接受推送,向正在推送的人打印警告(最好是知道该怎么做的同一个人,否则警告是无用的)。
    • updateInstead:再检查一件事(见下文)。

updateInstead模式下,事情变得复杂了。请参阅 the git config documentation and the description of the push-to-checkout hook in the githooks documentation 中的说明。简短的版本是,如果索引和工作树是干净的,通常会允许推送 并且 接收存储库将更新到新的分支提示。

不过,一般来说,您最好的选择是更简单的规则:就是不要这样做。2不要推送到有人在其中工作的存储库。这是个坏主意。仅推送到 从未 有人员在其中工作的存储库。将 Git 存储库配置为 "bare" 保证没有人可以在其中工作,因为它没有工作树。


1这要么是个好主意,要么是个糟糕的主意,或者两者兼而有之。无论哪种方式,它都完全不同。

2这可以称为反耐克规则。 :-)

虽然另一个答案中的 "Don't push to a repository that has humans working in it" 适用于常规的团队协作,但有时您需要在私有存储库之间分发未完成的更改,并且在服务器上使用裸存储库对它来说是多余的。你可以做到这一点,而不必通过推送到远程命名空间来处理推送到当前分支的问题:

push user@workstation:src/project 'refs/heads/*:refs/remotes/my-laptop/*'

这样,当您 return 到 workstation 时,您可以合并或挑选来自 my-laptop/ 命名空间中远程分支的更改。它还表明该工作已在另一个存储库中完成,以后应该更容易导航。