寻求关于 Git 克隆和拉取请求的说明

Seeking Clarification About Git Clone and Pull Requests

我很难理解如何执行拉取请求,而且我不经常使用 Github。

我被要求 git 将一个存储库克隆到我的本地计算机上并进行一些更改。从那里,我应该执行一个拉取请求。但我对如何这样做感到困惑?是否可以只请求本地计算机上的文件? 或者我是否需要让我的本地文件成为它自己的 git 回购然后拉取请求?

如有任何帮助,我们将不胜感激。

为 github 项目做出贡献的基本方法是先分叉回购(右上角)。然后你克隆你的分叉存储库,在那里进行更改并推送它们。完成后,您可以打开一个拉取请求,将您的分叉存储库的更改合并到源。

不足之处:对于拉取请求,您需要在某个地方提供您的更改,其他存储库可以在其中拉取它们 from:

  • 通常你会在 Github 处 fork 存储库。将你的 fork 克隆到你自己的机器上,在它上面工作,提交,推送,开发时的正常内容。
  • 如果你可以推送到同一个存储库,你可以在其中创建自己的分支,检查它,处理它,推送到你的分支,这是通常的开发过程。

然后,一旦您的代码位于 Github,您就可以转到您的存储库和分支,然后单击“创建拉取请求”,并填写所需信息。

拉取请求让您可以告诉其他人您已推送到 GitHub 上存储库中分支的更改 - About pull request

步骤 1:Git 将项目克隆到本地计算机

For reference: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github/cloning-a-repository

第二步:新建分支,提交代码修改,推送到远程Github

For reference: https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository

步骤 3:转到您的 Github 项目的网页 > select 选项卡 Pull requests > 单击按钮 New pull request > 选择您创建的分支 compare 和您要合并到的目标分支 base,就是这样!

For reference: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests

*Step 4: 可能有merge conflict warning,请在本地修复merge conflict,按照Step 2 push到远程

第 5 步:您的项目成员将审查您的代码更改,一旦他们批准拉取请求,您的代码更改将成功合并到基础分支。

从你的问题中不清楚你对 Git 了解多少。 GitHub 增加了一层额外的复杂性,但 Git 本身已经相当复杂了。

让我先回顾一下 high-speed 您需要了解的有关 Git 的事情:

  • Git是一个分布式版本控制系统,其中多个用户可以拥有每个存储库的多个完整副本。这里的整体单位是“版本库”,我们一般使用git clone复制一个版本库。然后我们将存储库的各种副本称为“克隆”,尽管每个副本实际上都是一个独立的存储库。

  • 存储库内部存储单元是提交。由某个存储库构成的克隆以所有相同的 提交 开始,并且在某个时刻同步的两个克隆可以通过将它们相互连接来 re-synchronized。一份指定为发送方,一份指定为接收方;发送者发送接收者需要但缺少的任何提交,然后发送者让接收者设置一些名称来记住这些提交。这个 t运行sfer 总是 one-way:例如,接收者永远不会转身开始发送。 (因此,完整的 re-sync 可能需要两个单独的操作。)

  • 每个提交都有一个非常大的(目前是 160 位)唯一编号,用 hexadecimal 表示。这些被称为 哈希 ID,它们看起来 运行dom,但实际上它们是内部对象内容的加密校验和。提交对象有额外的支持对象,这些对象会根据需要自动包含在同步 t运行sfer 中;像提交一样,它们有哈希 ID。 (标签对象就在这个设置之外,但为了简化我们将简单地忽略它们。请注意,树和 blob 对象哈希 ID 对于它们的对象是唯一的,但它们在许多提交中可以是 re-used;这只是提交他们自己是真正独一无二的。)

  • 提交存储两件事。每个提交都有每个源文件的完整快照,加上元数据,或关于提交本身的信息。元数据包括一些早期(父)提交或根据需要提交的原始哈希 ID,以便提交本身形成一个 backwards-looking 链。这个 Git 存储库中的历史:没有文件历史,只有 历史的提交。

  • A b运行ch name(或者任何其他名字,虽然 b运行ch 名字有点特殊)在任何 Git 存储库仅包含一个哈希 ID。特别是 B运行ch 名称被限制为仅包含 commit 哈希 ID。因此,b运行ch 名称标识 b运行ch 中的 last 提交。从那里开始,Git 向后工作,使用每次提交中保存的哈希 ID,向后工作,一次一个跃点,到第一个提交。 Adding a commit to a b运行ch 因此只需创建一个新的提交对象,其父哈希 ID 是哈希 ID提交的当前,最后一个 in b运行ch,然后更新 b运行ch name 来存储这个新的 last-commit.

    的哈希 ID
  • 因此最好将存储库视为由两个数据库组成:

    • 对象数据库保存所有对象。 T运行sfers——git pushgit fetch——在两个存储库之间,包括从两个存储库之一中挑选出丢失的对象,并将它们发送过来。这样,对象就被共享了。因为对象的哈希 ID 是对象内容的加密校验和,因此哈希 ID 是唯一的 那个对象,两个 Git 可以简单地交换哈希 ID弄清楚要发送什么。

    • 姓名数据库 包含 name-to-hash-ID table。这个table是分享的;每个存储库都有自己独立的 b运行ch 名称、标签名称等。

    将提交(和其他对象)从一个存储库发送到另一个存储库的过程以 接收 存储库更新一些名称以记住 每个 b运行ch 的最后一次提交。由于 Git 通过使用名称查找提交,如果接收存储库 更新任何名称,则接收方无法找到提交。

最后一点让我们了解 git fetch(“从他们那里获取提交”)与 git push(“向他们发送提交”)背后的复杂性。作为一般规则——有一些特定的例外,尤其是 GitHub“fork”操作——当我们从其他 Git 存储库 fetch 时,我们告诉我们的 Git取他们的名字运行ch as-is。如果我们在 our b运行ch 上做了一个名为 feature 的新提交,并且他们做了一个 different他们的 b运行ch 上的新提交名为 feature,会有问题:

          H   <-- our "feature"  (commit H is our new commit)
         /
...--F--G   ["feature" used to name this commit when we both started]
         \
          I   <-- their "feature" (commit I is their new commit)

单个名称 feature 可以 select 仅一次提交 。如果它 select 在两个存储库中提交 FG,那很好。但是我们现在已经添加了我们的新提交 H,并且他们已经添加了他们的新提交 I。 (这些单个大写字母代表真正的哈希 ID。)一个名称 feature 可以 select H,我们从中返回 G,然后 F, 等等;或者它可以 select I,从那里我们回到 G,然后是 F,依此类推。它不能select 两者.

所以,当我们 运行 git fetch origin 时,我们所做的就是告诉我们 Git:不要取他们的名字 运行ch as-is。 更改 它们。把他们的 feature 变成我们的 origin/feature,因为我们称这个为另一个 Git origin(名字 origin 是一个 remote,这是我们最初设置克隆时使用的。)我们让 Git 创建或更新我们的 origin/feature,留下我们的 b运行ch name feature 单独,所以我们得到:

          H   <-- feature
         /
...--F--G
         \
          I   <-- origin/feature

通过使用两个不同的名称,我们允许Git记住两个不同的提交哈希ID。如果他们添加更多提交,甚至 从他们的存储库中删除 提交 I,那没问题:

          H   <-- feature
         /
...--F--G
         \
          I--J   <-- origin/feature

甚至:

          H   <-- feature
         /
...--F--G   <-- origin/feature
         \
          I   [abandoned]

像这样的废弃提交仍然存在于我们的存储库中,只是变得很难找到了。 (最终,如果它被遗弃的时间足够长,git gc 会真正将其删除。)

然而,git push 命令并不是这样工作的。当我们 运行 git push origin 时,我们 Git 将我们的提交发送到他们的 Git,后者将它们存储在他们的存储库中(技术上是一种 qua运行齿区域最初)。然后我们要求他们的 Git 设置他们的 b运行ch name。所以我们开始:

          H--K   <-- feature
         /
...--F--G   <-- origin/feature

并发送提交 HK,然后要求他们设置他们的 b运行ch 名称 feature指向提交 K。只要他们的 b运行ch 名称 feature——我们在这里看到反映为我们的 origin/feature——仍然指向提交 G,他们这样做是“安全的” , 添加提交 HK。但是如果他们的 feature 指向某个提交 IJ,那是 不安全的 ,他们将拒绝我们的请求。 (事实上​​我们的提交 HK 进入了一个隔离位置 运行 然后他们很容易将它们从他们的对象数据库中弹出:在 [=526= 这样的地方很重要]Hub 接收到 lot 数据,然后拒绝其中的一些数据,例如,因为文件过大。)

无论如何,如果一切顺利并且他们接受我们的推送,我们的Git现在将更新我们的origin/feature,因为我们知道他们移动了他们的feature 指向 K:

...--G--H--K   <-- feature, origin/feature

现在一切正常:两个存储库同步。

有时 Git集线器不会增加复杂性

现在假设您要向其发出拉取请求的 GitHub 存储库是:

ssh://git@github.com/user/repo.git

(如果您愿意,可以将 https://github.com 与个人身份验证令牌一起使用,但我将在此处使用 ssh 作为示例。)

现在,让我们假设您可以push访问这个存储库,这样您就可以创建一个新的b运行ch name 这个存储库中。

要在 GitHub 上提出拉取请求,您需要:

git clone ssh://git@github.com/user/repo.git

这会将来自 repo.git 的所有 commits 和 none b运行ches: 相反,您的 Git 将添加名称 origin,引用 ssh://git@github.com/user/repo.git,并重命名他们所有的 b 运行ch names to your origin/* remote-tracking names.

然后,因为你没有说 -b main-b develop 或其他什么,你的 Git 会问他们的 Git 他们叫什么 b运行ch推荐。他们会说 main 或他们说的任何内容。您的 Git 现在将在您的克隆中创建一个名为 main(或其他)的 b运行ch,指向与您的 origin/main(或 [=499)相同的提交=]).

最后,您的 Git 将检查这个特定的提交,以便您可以处理它。我假设你知道你需要知道的一切关于 在本地处理提交,以及关于在本地创建新的 b运行ch 名称,等等。

最终,您将在您的 存储库中有一个或多个新提交。您现在需要 t运行sfer 这些提交到您正在调用 origin 的 GitHub 上的存储库。为此,您需要使用 git push.

正如我们在上面看到的,您的 git push 将与他们的 Git 协商——他们的软件与他们的存储库对话,在 GitHub 上——找出 wich 承诺发送,然后将发送它们以及所需的任何其他对象。然后你的 Git 会要求他们在 他们的 存储库中设置一个 b运行ch 名称。您可以通过多种方式在其存储库中选择 b运行ch 名称:

  • 如果你没有什么特别的,就运行:

    git push -u origin HEAD
    

    或类似的,您的 Git 将要求他们的 Git 设置一个与您在存储库中具有相同名称的 b运行ch。这既简单又方便:您只需预先仔细选择您的(本地)b运行ch 名称。

  • 或者,您可以 运行:

    git push -u origin HEAD:newbranch
    

    或类似的。这将使您的 Git 要求他们的 Git 设置 b运行ch 名称 newbranch。您可以在此处使用任何有效的 b运行ch 名称,例如 hi/there/new/branch 或其他名称,但通常需要保持简单。

如果他们接受这个操作,你就快完成了:你现在需要使用 GitHub 本身——而不是 Git,它不知道 GitHub 是什么”拉取请求”是——在 GitHub 上创建拉取请求。这通常涉及使用他们的 Web 界面:您导航到 github.com/user/repo.git 并按下各种点击按钮来创建拉取请求,选择您的 b运行ch(刚刚创建)作为源和其他一些 b运行ch 作为“基础 b运行ch”。如果一切顺利,这将在 GitHub 上创建 PR 并提醒 github.com/user/repo.git 的管理员有一个新的拉取请求。

(GitHub 也有一个 gh 脚本,你可以 运行,它使用 curl 从命令行执行所有这些操作。我还没有我自己用过。)

有时 Git集线器会增加复杂性

我们上面的谓词,需要使这一切简单,是你有直接推送访问ssh://github.com/user/repo.git(或 https 变体)。如果你不这样做怎么办?

在这种情况下,GitHub 提供了一条简单的前进道路。您从 GitHub 的 FORK 按钮开始(,它执行此操作然后对您的笔记本电脑执行 git clone,一次全部)。这个 GitHub 端操作的核心是 git clone,但有一个很大的不同:A GitHub fork clone 复制所有 b运行 ches 也留下一个 link 回到原来的样子。

也就是当我们运行:

git clone -b somebranch ssh://git@github.com/user/repo.git

我们在笔记本电脑上得到了一个(本地)Git 存储库,其中所有 提交 已被复制,但没有 b运行ch已被复制;相反,一个 new b运行ch 是根据我们的 -b 论点创建的,或者没有。 GitHub 上的“他们的”存储库不知道我们克隆了什么。 我们的克隆到他们的存储库有一个弱的link,因为我们的Git存储ssh://git@github.com/user/repo.git在名称 origin 下,以便我们以后可以使用 origin 来引用它。

但是 GitHub fork-clone 做了一个克隆,其中 all b运行ch 名称被复制,并且有一个更强大的link 在他们的 GitHub 存储库和我们新的 GitHub 分支之间,双向。他们可以看到我们分叉了他们的 GitHub 存储库,我们的分叉 link 到他们的 GitHub 存储库。此 link 在 存储库本身中是不可见的: GitHub 将此 linkage 信息存储在别处。

现在我们有 我们的 叉子,例如 ssh://github.com/us/repo.git,我们使用它的方式是将我们的叉子克隆到我们的笔记本电脑上:

git clone -b somebranch ssh://git@github.com/us/repo.git

这将 ssh://git@github.com/us/repo.git 存储在我们的克隆中,名称为 origin

出于各种目的,我们最终希望在我们的笔记本电脑克隆中以另一个名字记住 ssh://git@github.com/user/repo.git。按照惯例,第二个名字是 upstream。我认为这是一个糟糕的名字,但没有更好的建议,而且在某些方面最好随波逐流,所以我也将使用 upstream。我们输入我们的克隆:

cd repo

并使用git remote add添加第二个远程名称:

git remote add upstream ssh://git@github.com/user/repo.git

我们现在可以运行:

git fetch upstream

让我们的 Git 调用 ssh://git@github.com/user/repo.git——存储在名称 upstream 下的 URL——并获得他们拥有但我们没有的任何提交,然后 create-or-update 我们所有的 upstream/* remote-tracking 名字。

如果我们足够快地完成这两个步骤,将不会有任何新的提交:当我们 运行 git clone ssh://git@github.com/us/repo.git,其中包含所有 commits(以及所有 b运行ches)。所以这一切真正要做的是创建 upstream/*,所有这些都将匹配 origin/*。这可能会让您想知道:我们为什么要打扰?

答案是:随着时间的推移,他们将向 upstream (github.com/user/repo.git) 存储库添加新的提交,不会出现在我们的fork中(github.com/us/repo.git). We will need to transfer these to our laptop, with git fetch upstream, and then send them back to github, with git push origin`.1

(这些都是以后的事,除非 repo.git 真的很活跃,我们花了很长时间才能 fork-and-clone 我们现在必须这样做。但请记住。)

现在我们在 GitHub 上有我们的克隆——在 github.com/us/repo.git——我们的克隆在我们的笔记本电脑上,我们像往常一样进行:我们做出新的提交,测试它们,等等,使 b运行 不断前进,最终得到一些我们想放入 GitHub Pull Request 中的新提交。为此,我们:

  • 将我们的新提交发送到我们的分支,使用一些新的 b运行ch 名称:这就像更简单的情况一样工作;然后
  • 使用 GitHub 的“拉取请求”点击按钮,或 gh 命令行,从 我们的GitHub 分支,他们在 GitHub 上的原始版本。

简而言之——如果还不算太晚的话——我们创建了一个 GitHub 分支,这样我们就有了一个 GitHub 存储库,我们可以 git push 提交和设置起一个 b运行ch 的名字。我们从他们的 GitHub 存储库中创建了这个分支,因为我们不允许在他们的 GitHub 存储库中设置 b运行ch 名称 in提交是共享的; b运行ch 名称不是。


1这造成了明显的低效率:让 GitHub 自己做这件事不是更好吗?答案是 是但 but 部分与 b运行ch names 何时以及如何在我们的 GitHub 分支上获得有关更新。如果 GitHub 使用 remote-tracking 名称,这将不是什么大问题,但他们没有。


他们接受你的 PR 之后

一旦他们接受你的拉取请求,你可能想要更新你的笔记本电脑克隆。如果你必须制作一个 GitHub 分支,这里会出现一个并发症:现在你需要 git fetch upstream,而不仅仅是 git fetch origin,然后你可能想要 git push origin更新您的 GitHub 分支。请参阅上一节。

还有更多。如果他们 接受您的 PR,他们可以使用三个绿色点击按钮之一:

  • MERGE 只是进行普通的 Git 合并。一切都很好。

  • REBASE AND MERGE 有 GitHub copy 你所有的提交到新的、不同的提交。这是一个令人头疼的问题,因为现在你的提交,存在于你的笔记本电脑上,也许在你的GitHub fork中,将被淘汰以支持他们的新和supposedly-improved 提交。是否同意这一切是你的选择,但如果你想和他们玩得开心,你将不得不这样做。

    没有简单方便的方法来更新您的笔记本电脑和 GitHub 叉。相反,您必须使用 less-convenient 方法(我们不会在此处介绍)。对于大多数简单的情况,这只是丢弃您的 b运行ch 名称,然后从他们的名称重新开始的问题。

  • SQUASH AND MERGE 将所有 your 提交变成他们拥有的一个大提交,在他们的存储库中。这类似于 rebase-and-merge 按钮,因为您现在必须放弃您的提交以支持新的单个提交。更糟糕的是,除非您的提交 开始时的一次提交,否则很难实现自动化。不过,这通常只是丢弃 your b运行ch 名称的问题。事实上,如果你曾经在本地使用 git merge --squash,这与你在本地需要的过程相同:squash merge 意味着“丢弃其他”,在比单独提交更高的级别.2

当然,他们可能不会接受 你的 PR,在这种情况下,你可能必须自己用 new-and-improved 提交替换提交。无论您是通过自己的 GitHub 分支执行此操作,还是通过直接推送到您拥有推送权限的 GitHub 存储库,您通常都希望使用 git push --forcegit push --force-with-lease3 更新你的 GitHub b运行ch,在你更换之后,在你的笔记本电脑中,一些提交与其他 new-and-improved 提交。

如果他们使用 rebase-and-merge 或 squash-and-merge 序列,这个过程与你将使用的过程非常相似:它们都涉及丢弃旧的 b运行ch (及其提交)支持新的 b运行ch(及其提交)。删除 b运行ch 和创建一个同名的 force-pushing 之间的区别是……基本上 nonexistent。


2理论上可以设置一个 squash-merge 序列,不涉及丢弃压缩的提交。实际上,这太难了;别这样。

3--force-with-lease选项是--force先进行安全检查。有关更多信息,请参阅其他 Whosebug 问题和答案。