为什么不应该使用git pull?

3

我是Git的新手,大多数时间我使用git pull origin <my-branch> 来获取远程仓库中的更改。

然而,随着我积累了一些经验,我发现使用git fetch更受欢迎。但是,在阅读了几个主题(例如What is the difference between 'git pull' and 'git fetch'?Git: Fetch and merge, don’t pull)后,我感到困惑并且需要澄清一下是否有除了在获取更改之前检查更改之外的有效理由来使用git fetch。

总体思路是,git pull相当于git fetch + git merge,但是肯定存在一些缺点等。

所以,请您解答以下问题:

1.我应该如何从远程更新本地分支?

2.就我所看到的,git pull origin <my-branch>git pull origin之间的区别是,后者会获取除了<my-branch>以外的所有分支。这是真的吗?我应该选择哪个?


这回答了你的问题吗?“git pull”和“git fetch”的区别是什么? - sommmen
很遗憾,我读了它但更加困惑了 :( - user17862362
@sommmen,请问您对这个问题有什么澄清吗? - user17862362
我认为 git pull 在任何形式下都没有问题。你只需要了解它的作用,看它是否符合你当前的需求即可。 - Marek R
它可以使用,但我想使用最合适的方法。有什么建议吗? - user17862362
6个回答

5
git fetch 是一个命令,用于让本地 Git 从原始仓库检索最新的元数据信息(但不进行任何文件传输)。它更像是检查是否有可用的更改。
git pull 命令则既可以检索最新的元数据信息,同时也会将远程仓库中的更改(副本)复制到本地仓库中。
需要记住的是,在工作站上通常至少有三份项目的副本:
  1. 一份副本是你自己的带有自己提交历史记录的仓库(已保存的那个)。
  2. 第二份副本是你的工作副本,在这里你正在编辑和构建(尚未提交到你的仓库中)。
  3. 第三份副本是你本地的“高速缓存”副本远程仓库(很可能是你克隆自己的原始仓库)。
你可以使用 git fetch 命令查看自从上次拉取以来远程仓库/分支中的更改。这对于在执行实际拉取之前进行检查非常有用,因为这样可以避免更改当前分支和工作副本中的文件(可能会导致丢失更改等问题)。
git fetch    
git diff ...origin
  1. 我该如何从远程更新我的本地分支?

使用git pull是安全的更新分支的方式。有时会出现冲突,这些情况属于边缘情况。最坏的情况是撤销git pull的更改。

  1. 据我所见,git pull origin "my-branch"和git pull origin之间的区别在于,后者可以获取除了“.”之外的所有origin分支。这是真的吗?我应该选择哪一个?

如果您使用git pull origin而不指定“my-branch”,git会将该值填充为您当前使用的分支。


好的解释,已点赞 ;) 但我想知道冲突状态,因为如果我使用 git fetch 然后 git merge,我如何避免使用 git pull 可能会遇到的冲突? - user17862362
1
这个答案有一些问题:git fetch确实会下载文件并将提交完整地更新到您的本地工作站,并更新远程分支(例如:origin/branch_name);但它不会更改您的本地分支。 - LeGEC
1
git pull 的潜在问题是,它可能会在不提前检查会发生什么的情况下(它一次运行 git fetch + git merge),破坏您的本地更改(如果有的话)。虽然通常这是您想要做的,但当事实证明这是错误的时候,撤消变得非常困难。 - LeGEC
如果你从一个干净的状态开始(在运行git pull之前强烈建议这样做),你可能会遇到你没有预料到的冲突,但至少你可以回滚到你仓库之前的状态。 - LeGEC

3

git pull:

  1. git pull运行git fetch,然后
  2. (不需要等待您确认!) 运行第二个Git命令。

如果你想执行两个 Git 命令,并且 git pull 将要执行的第二个命令是你想要执行的命令,那么使用 git pull 是可以的。

我个人经常喜欢在fetch命令和其他命令之间 插入 一些 额外的 Git 命令。但当使用 git pull 时这是不可能做到的,因为它不会等待你。因此我通常避免使用 git pull。(特别是我经常想运行git log来看看我正在处理什么内容。)

我还发现对于新手来说,他们认为 git pull 是有点神奇的。通过使用分开的两个步骤,而不是使用 git pull,他们学会如何使用 Git。使用 git pull,他们就无法学习如何使用 Git。所以我鼓励新手使用分开的命令。这不仅有助于强调“Git 不是魔法”的部分,而且还有助于区分 git pull 运行的第二个命令是你自己选择的:

  • 您可以选择让 git pull 运行 git merge
  • 您可以选择让 git pull 运行 git rebase

这两个命令都是合并工作,但它们执行方式是非常不同的。如果您在自己的代码库中没有做任何工作,则与别人共享的工作内容是得到别人的工作,所以无论使用哪个命令都无关紧要。但是,如果您在自己的代码库中做了一些工作,则会有所不同。

当您将这两个命令分开时,其区别就更加明显了:

  • git merge 表示将我的工作与他们的工作合并: 将“nothing”与“something”合并=“something”;将“something”与“something else”合并=“some third thing”。
  • git rebase 表示在他们的工作之上重新执行我的工作: 在“something”之上重新执行“nothing”=“something”,但是在其他的情况下,您可能会看到这个效果(如果不行,请查阅关于git rebase的相关信息)。

针对您的具体问题:

我该如何从远程更新我的本地分支?
这要看你想要哪个结果以及你对第二个命令有多确定。
据我所见,git pull origin <my-branch>git pull origin的区别在于,后者会获取除了<my-branch>以外的所有分支,大多数情况下是这样。这时我们真正需要将git pull分解成两个步骤,并观察它给出了什么。
当你运行git pull时,可以提供选项。例如,以下两种调用git pull的方法都是有效的:
git pull --rebase

git pull --ff-only

这些选项是矛盾的,因为--rebase表示git pull应该在第二个命令中运行git rebase,而--ff-only表示git pull应该向第二个命令提供--ff-only选项,这意味着它应该运行git merge而不是git rebase
所以,一些选项控制着pull应该使用哪个第二个命令。其他选项被传递第二个命令。还有一些选项被传递到第一个git fetch命令。这有点令人困惑,这也是学习git fetch 首先的又一个原因。
您还可以提供参数,例如您在此处建议的<my-branch>。您提供的所有非选项参数都将传递给git fetch。通过---标识可将参数与选项区分开来。(单横线-选项是单个字母,例如-j-4;双横线--选项是多个字母,例如--rebase--show-forced-updates。)
如果您提供像origin<my-branch>这样的参数,它们将传递给git fetch,并影响git fetch的操作方式。没有参数时,git fetch将:
  • 找到正确的远程连接(通常是 origin):一个"remote"是一个简短的名称,用来访问另一个Git软件,这里会从另一个Git仓库读取内容。在这种情况下,你想要访问GitHub、Bitbucket或GitLab等平台上的某个Git软件,它们上面有你之前创建的Git仓库所在的Git仓库。现在你想要访问相同的Git仓库,并查看它们是否有任何你的Git仓库还没有的新提交。(那些提交是如何出现的?我们稍后再担心这个问题。)

  • 调用该软件并连接到该仓库。该仓库有它的分支和它的提交。该仓库中的分支不是你的分支!他们是他们的分支。它们可能在其中存储了不同的提交哈希值。

  • 根据存储在其各个分支名称中的哈希ID,确定它们有哪些提交,而你没有,并决定哪些提交是你想要在你的仓库中的。

如果你在git fetch命令中没有列出某些分支名称,则你的Git会默认更新所有分支的所有副本。因此,你的Git将检查它们的mastermaindevelopfeature/shortfeature/longfeature/tall等分支。你的Git将确定它们是否有任何你没有的新提交,并将这些提交带到你的Git仓库中。

由于提交是用全局唯一标识符编号的,因此你的Git(你的运行在你的仓库上的软件)现在将拥有所有他们的提交,并使用相同的编号。你的Git还将拥有他们没有的全部自己的提交。现在,你的Git已经获得了所有他们的提交,你的Git将创建或更新所有你的远程跟踪名称:origin/mainorigin/master代表他们的mainmasterorigin/develop代表他们的develop,以此类推。你的Git通过在每个他们的分支名称前添加remote名称 origin 来构建这些名称。

这些remote-tracking名称构成了你的Git对于它们的分支上次被你接收到(即获取)的位置的记忆。所以不带参数的git fetch会更新所有这些远程跟踪分支,因为不带参数的git pull实际上是调用了不带参数的git fetch,所以你的所有origin/*名称都将得到更新。当使用一个参数时,像git pull origin,同样的事情会发生,只不过现在你明确地表达了你想要使用名称为origin的远程仓库。如果这是你唯一的远程仓库——这是一种典型的设置——那么这将完全相同;任何其他的名称,比如git fetch belgium或其他什么的,都将导致错误。
但是如果你运行git fetch origin develop,那么这告诉你的Git,对于这个git fetch操作,你希望你的Git访问他们的Git,查看它们的所有分支,但是限制更新只更新你的origin/develop所需的提交。如果他们的main有一个新提交,你不会更新你的origin/main。(你几乎肯定希望或必须稍后这样做,所以这并没有真正为你节省多少时间。实际上,由于Git优化获取的方式,这可能会导致稍后需要更多时间,而不是一次性完成所有操作。但如果你想要,这个选项是存在的。)
由于git pull所有参数传递下去,因此git pull origin develop指示你的git fetch步骤限制其自身到名为develop的远程分支。(同样的操作也会更新你的origin/develop
但现在第二个命令起作用了。在运行了git fetch之后,无论它使用了什么额外的选项和参数,你的git pull现在运行了你选择的第二个命令。(你确定自己选择了一个吗?总是确保你知道Git将在这里运行哪个第二个命令!大多数人都会设置一个半永久的,这样他们就知道了。)第二个命令可以是:
git rebase [options argument(s)]

或者:
git merge options argument(s)

Git 的 pull 命令 通常会在此处传递一些选项和/或参数。特别地,对于 git merge 命令,它会传递:

-m "merge branch '<branch>' of <url>"

设置合并信息,然后传递您引入的最新提交的原始哈希ID。对于rebase,它可能会传递--autostash,也可能会传递提交哈希ID(或者可以让rebase自行找出@{upstream})。虽然您不需要了解所有这些内容,但值得记住的是,git pullgit merge功能提供了一些额外的操作,以设置合并消息。

这里还有一个最后的警告:

git pull origin br1 br2

对于新手来说,使用 不要使用 的方法是很诱人的。它运行了 git fetch origin br1 br2 然后运行一个 章鱼合并 (实际上是 HEAD, origin/br1origin/br2 的合并),除非你真的知道自己在做什么,否则你不想要一个章鱼合并。

这会导致几个底线

如果你将 git pull 设置为 始终运行 git rebase,那么你自己运行 git fetch 然后再运行 git rebase 和只运行 git pull 没有太大区别。这是因为没有合并消息需要修改。但在执行此操作之前,请确保你知道 rebase 做了什么:rebase 比合并更加复杂。

如果你将 git pull 设置为 始终运行 git merge,则 pull 执行的抓取-合并操作具有一定的优点 (?) ,可以将合并消息设置为比两个单独的命令得到的默认消息略微好一些。比较:

merge branch 'smörgåsbord' of ssh://github.com/swedish/meatballs.git

vs:

merge branch 'origin/smörgåsbord'

两者都没有提供任何有用的信息,但有些人可能更喜欢其中一个。

小心使用 git pull <remote> <branch1> <branch2>,几乎肯定会出错(尽管如果你设置为rebase,这应该只会给你一个错误;在此情况下进行rebase没有意义)。

如果你想要在两个命令之间运行一个命令(例如git log),以便选择使用哪个第二个命令,则不能使用一次完成所有操作的 git pull。这就是我避免使用 git pull 的原因和时间。

除此之外,它们基本上是相同的东西,一旦你知道 git pull 只是为你运行两个Git命令。


非常感谢这些精彩的解释。由于我没有足够的经验,通常会避免使用rebase。因此,我决定使用fetch和merge。在这种情况下,我想知道人们是否按照@VonC的建议使用fetch后跟随merge,或者在运行fetch时是否有设置可以自动执行merge? - user17862362
没有这样的设置。但是,有时 git merge 执行快进操作而不是合并,而 git fetch 可以执行快进操作。但在非常熟悉所有这些含义以及 Git 如何使用 refsrefspecs 之前,请勿执行此操作。我实际上喜欢 git pull 执行仅快进操作,当失败时则失败合并;自 Git 2.29 以来,现在有一个设置可以做到这一点,但在 2.34 之前它有些问题。因此,我通常不会在这里写关于它的内容:它太新了。 - torek
那么如果我想使用 git fetch,那么我应该通过以下两个命令来使用它:git fetch origin <feature-branch>,然后(当我在这个特性分支上时)git merge <feature-branch>。这是正确的吗? - user17862362
我使用:git fetch,然后是 git merge。这取决于一个前提条件,即当前分支需要有Git所谓的上游(upstream)设置。如果没有,我建议使用git fetch(不带任何额外参数)和git merge origin/somebranch。请注意,在使用拆分变体时,您需要提供在另一个Git中看到的本地内存名称。在使用git pull origin somebranch变体时,您需要提供在另一个Git中看到的其他Git的名称 - torek

1
我是Git的新手,大多数时候我使用git pull origin从远程仓库获取更改。它们是执行不同操作的不同命令,fetch会加载所有新的远程提交记录,您可以查看这些提交记录,当您想要实际应用这些新的远程提交记录到本地分支时,运行pull命令。明确一点,运行pull命令相当于运行fetch和merge,将远程更改合并到本地。现在我感到困惑,需要澄清是否有有效的理由优先使用它,除了在获取更改之前检查它们。您不是使用一个或另一个 - 您同时使用它们两个。没有偏好 - 您先fetch然后再pull - 或者直接pull。您看到的偏好可能来自fetch是安全操作 - 它不会更改任何内容。Pull确实会更改您的本地文件,您可能会遇到合并冲突。在本地功能分支上运行pull是可以的,但可能会有合并提交记录。关于git pull origin和git pull origin之间的区别,我不确定哪个更好。

非常感谢,已点赞。但是,“在本地特性分支上运行拉取是可以的,但可能会有合并提交。”--> 如果我使用fetch,我不会得到这些冲突吗?有什么区别?你的意思是如果我使用fetch,我会逐个提交地将获取的分支合并到我的本地分支中吗? - user17862362
@Owl fetch仅更新远程跟踪分支,不会更改本地分支。因此,不可能发生冲突,因为fetch不执行合并或变基操作。 - VonC

0

我并不是基于文档编写的,而是基于一些经验。虽然不多,但或许有所帮助。

  1. Git pull 是应该使用的命令,用于将远程代码拉取到本地。
  2. git fetch 只获取元数据,而不是实际代码。

通常情况下,单个存储库永远不会使用 fetch 命令,但如果你正在 fork 或者拥有多个远程存储库,则只有 fetch 有用或经常被使用。


是的,这些是我观察到的要点。但是,我不确定为什么我应该更喜欢使用 git fetch + git merge 而不是 git pull - user17862362
我的第二个问题呢? - user17862362
@ManyakJain 朋友?请问有回复吗? - user17862362
我认为你应该多花些时间了解一些常见的点,并尝试使用可视化 git,这样你可以更好地理解。因为 fetch,merge 和 pull 是完全不同的概念,只有当你有清晰的思路和清晰的画面时才能掌握它们。你应该在可视化 git 中看到它以便理解。 - Mayank Jain
@ManyakJain 非常感谢,朋友。你有没有推荐一些在VSCode或IntelliJ中以彩色显示git分支的插件? - user17862362
只需在谷歌中搜索“visualizing git”,您将会得到许多网站,这些网站会以图形化的方式展示您的所有练习内容。在那里进行练习,这将有助于您更好地学习和理解。 - Mayank Jain

0
我应该如何从远程更新我的本地分支?
我通常使用git pull(默认情况下获取所有分支)。
但是我首先设置了自Git 2.6以来
git config --global pull.rebase true
git config --global rebase.autoStash true

这样,在获取之后,简单的git pull会触发将我的本地提交变基到获取的远程分支之上。

我唯一看到的缺点是可能需要多次解决冲突,即在git pull之后再进行git pull。但如果您处于这种情况(相当罕见),那么git rerere就可以避免这种情况。
请参阅 "什么是git-rerere,它是如何工作的?"

关于优点:请参阅 "使用git pull --rebase是否比使用git pull --ff-only更好".


嗯,看起来非常棘手和有用。我认为使用这种方法,就不需要使用2个命令(fetch + pull),并继续使用pull命令,以提供fetch命令的优势。这是真的吗? - user17862362
你能否再详细解释一下,“当你想要将本地提交(尚未推送)重新应用于更新的(已获取)上游分支”这个问题? - user17862362
顺便赞成你的有用解释 ;) - user17862362
@Owl fetch 永远不会合并,只有 pull 会默认地获取并合并。在我的情况下,我更喜欢使用 fetch + rebase。 - VonC
然后我会使用这个配置与 git pull,但在使用之前,您能否请明确一下这种方法可能存在的缺点? - user17862362
显示剩余6条评论

0

如果我要获取别人推送的新分支,我会使用git fetch。 假设我想查看Mary在她的featureB分支上的最新更改。

我会执行以下操作:

git checkout featureB
git pull origin featureB

这将获取featureB的最新元数据,并将其合并到我的当前featureB副本中。

但是,如果我从未拉取过featureB,则无法检出featureB。

如果我当前已经检出了自己的分支featureA,则不想执行git pull origin featureB,因为那会获取featureB,然后将其合并到我的featureA分支中。

因此,我这样做:

git fetch origin featherB
git checkout featureB

现在我在本地仓库中有一个名为featureB的分支。

既然我现在在本地仓库中有了featureB分支,下次想要从远程仓库更新它时就可以使用checkout命令。

假设我已经检出了featureA分支,现在想要获取featureB的最新版本。我可以执行以下操作:

git checkout featureB
git pull origin featureB

  1. 解释得很好,已投赞成票。现在有一个新的featureB副本-->你的意思是这个副本在我们的本地缓存中吗?
- user17862362
  1. 我不太理解这句话,请问能否再解释一下?
- user17862362
我编辑了原始帖子以澄清你所问的问题。 - Randy Leberknight

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