【问题标题】:Can I pull some files from a specific branch, modify them and push them to another branch in the same github repository? [duplicate]我可以从特定分支中提取一些文件,修改它们并将它们推送到同一个 github 存储库中的另一个分支吗? [复制]
【发布时间】:2021-05-05 11:48:40
【问题描述】:

我尝试了一些在 github 上创建的随机文本文件,将它们拉到我的计算机上,修改它们,然后我在 github 上创建了一个单独的分支,称为“另一个”。当我尝试使用 git 将修改后的文件从我的计算机推送到 github 时,我收到了以下消息:

error: src refspec another does not match any<br>
error: failed to push some refs to 'https://github.com/username/repo-name.git'

我之前输入的内容: git push origin another

【问题讨论】:

  • "在 github 上创建了一个单独的分支,叫做 'another'" 你在 Github 上创建了这个分支;它还没有在你的本地仓库中,所以你不能推送它。

标签: git github


【解决方案1】:

Git 并不是真正的关于 文件,git pull 也不会提取文件。但是,您可能可以实现您可能想要的。这里真正的问题在于弄清楚你想要什么,在 Git 的上下文中,它存储 commits,而不是文件。

不过,首先让我们来解决这个问题:

error: src refspec another does not match any

这意味着您没有有一个名为 another 的分支。考虑到你刚才所说的,这是有道理的:

我在 github 上 [创建] 了一些随机文本文件,

不完全清楚是如何你这样做的(见Minimal Reproducible Example),但我们假设这在GitHub上创建了一个或多个新的提交,每个都在某个分支上—mastermain 也许。

...把它们拉到我的电脑上,

假设您在这里运行:

git pull

在您首次创建的存储库中:

git clone <url>

其中指定的 url 是在您的计算机上拥有您的 Git,与 GitHub 的计算机通信以从您的 GitHub 存储库获取提交并将提交提交到您的 GitHub 存储库的地址。这个git pull 将从您的 GitHub 存储库获得 commits。这些提交将包含您创建的文件。这些提交在它们所在的任何一个分支上(任何给定的提交都可以在此处的一个或多个分支上,甚至在某些情况下——尽管不是这个——在 no 分支上)。假设您在此处使用新的 main 分支名称,以便这些新提交位于 main(而不是任何其他分支)上。

在“获取新提交”步骤 (git fetch) 之后,git pull 运行您选择的 Git 命令。如果你不告诉它,第二个命令将是git merge。在这种情况下可能适用的情况下,git merge 将执行 Git 所说的 快进合并,这在技术上根本不是合并。结果是您的main 分支(或任何名称)在您计算机上的您的 Git 存储库中,现在与main 分支(或任何名称)同步GitHub 上的存储库。

(我们需要在这里相当精确,否则这些东西都不起作用,就像你刚才看到的那样。)

[我]在本地修改了[这些文件],然后我在 github 上创建了一个单独的分支,称为“另一个”。

请注意,在快进操作之后,您的 Git 存储库将具有与 GitHub 存储库相同的 commits。您自己的 Git 将在本地更新分支名称上运行 git checkout — 再次,我仍然假设这里将其命名为 main,尽管它可能是 master 或其他名称。

这个git checkout 步骤创建或更新了您看到和修改的可见和可编辑文件。这些文件不是存储库中.要将它们放入存储库,您必须在本地运行 git addgit commit。这将创建一个新的提交。提交是存储库中的内容。

你没有提到做过,所以我假设你没有。在您拥有之前,这些文件只是您计算机上的文件。它们根本没有存储在 Git 中。他们只是在那里为你工作。您将需要 git addgit commit 步骤来进行新的提交。

同时,让我们回到这个:

然后我在 github 上创建了一个单独的分支,称为“另一个”。

这会在 GitHub 上的另一个存储库中创建一个分支。它对这里的 your 存储库没有影响。所以你没有名为another的分支。

当我尝试推动时

我们必须猜测你使用了什么命令,因为你没有告诉我们,但大概是:

git push origin another

这是明智的,但会立即给你刚刚看到的错误,因为——正如我们已经指出的——没有这样的分支。

处理 Git 有点混乱和复杂

这是因为分布式源代码控制从根本上说是复杂的。

Git 的方法是根据 commits 来工作。您所做的每个提交都有一些有趣的属性:

  • 每个提交都有编号。这些数字不是简单的连续的东西——Git 不会提交 #1,然后提交 #2,等等——而是看起来随机的,但根本不是随机的哈希 ID。哈希 ID 是每个提交内容的加密校验和,并且保证对于该特定提交是唯一的。 (例如,为了帮助实现这一点,Git 会在每次提交上加上日期和时间戳。)

  • 每个提交都包含两个部分,因为它是:

    • 其中一部分是每个文件的完整快照。这些文件以压缩、只读和去重的格式存储,因此如果您有很多文件,但只更改一个,则您所做的新提交只会重复使用所有其他文件。这样可以节省很多空间。但是,这确实意味着没有人可以更改任何已提交的文件。 (这也由编号方案强制执行。)

    • 每个提交的另一部分是元数据,或关于提交本身的信息。例如,这包括您的姓名和电子邮件地址。它包括我提到的日期和时间戳。它包含一条日志消息,您应该在其中解释为什么您做出了提交。而且,对于 Git 的内部目的而言至关重要的是,提交的元数据包括一些较早提交的原始哈希 ID。

大多数提交记录一个较早提交的哈希 ID。 Git 将其称为提交的 parent。提交的父级是您在进行 this 提交时使用的提交(通过git checkout)。结果是,在从一些git checkout 开始的一系列git commit 操作之后,您有一个提交字符串,一次一步地向后指向每个先前的提交:

... <-F <-G <-H

这里,H 代表此提交链中最后一次 提交的哈希 ID。 H 包含快照和元数据,H 的元数据包括早期提交 G 的哈希 ID。我们说H 指向早前的提交GG 又包含快照和元数据,因此G 指向FF,同样,有一个快照,并向后指向。

鉴于这种结构,Git 只需要一件事即可找到整个链:它需要链中 last 提交的哈希 ID。 Git 通常将此哈希 ID 存储在分支名称中:

...--F--G--H   <-- main

名称 main 包含哈希 ID H,它允许 Git 提取提交 H(包括其所有文件)供您在技术上位于存储库本身之外的工作区域中处理/使用。这是您的工作树工作树

您可以在这里创建任何您喜欢的新文件。您可以更新任何现有文件。您可以做任何您想做的事情,因为存储库之外的这个工作区实际上是你的。 Git 所做的只是:

  • 当您询问时,将文件提取到其中;和
  • 准备文件它提交,当你问的时候。

这两个“询问”可以使用各种命令,但两个明显的命令是 git checkout(它通过某个提交填充您的整个工作树)和 git add。签出步骤只留下 Git 知道的任何文件。毕竟,这些是您的 文件,而不是 Git 的。如果您创建了一个新文件 xyzzy 并且从未告诉 Git,Git 就会将其留在那里。

git add 步骤通过将一些文件复制到已设置的隐藏工作区来工作,Git 有时将其称为 暂存区,有时将其称为 index时间>。这个东西 - 索引或暂存区域 - 最初包含 current 提交中的所有文件,您使用 git checkout 签出,准备进入 next 提交。使用git add 告诉 Git 将一些工作树文件从你的工作树复制到暂存区,以便它准备好进入 next 提交。

通过这种方式,Git 的索引/暂存区域始终包含您的建议的下一次提交。当你运行git commit 时,Git 所做的是:

  • 收集任何必要的元数据(您的姓名和电子邮件地址,日期和时间的“现在”等);
  • 将暂存区域中的内容(Git 知道的每个文件的可替换但准备提交的副本)转换为新快照;
  • 将所有这些内容写成一个新的提交,使用当前提交的哈希ID作为新提交的父级;和
  • new 提交的新唯一哈希 ID 写入 当前分支

所以如果你添加一些新文件,和/或修改一些现有文件,然后运行git commit,Git 会转:

...--F--G--H   <-- main

进入:

...--F--G--H--I   <-- main

这就是你进行新提交的方式。

更多关于分支名称

正如我们刚刚在上面看到的,分支名称只是指向某个提交:某个链中的最后一个提交。

让我们从前面介绍的存储库开始:

...--G--H   <-- main

现在,让我们创建一个新的分支名称。为此,我们必须选择一些现有的提交。除了 latest 提交,没有什么特别的理由在这里选择,所以让我们使用那个:commit H。我们将使用新名称develop。现在我们有:

...--G--H   <-- develop, main

我们现在有一个问题:我们选择main 分支还是develop 分支?为了明确我们使用的是哪一个,让我们为两个分支名称之一附加一个特殊名称 HEAD

...--G--H   <-- develop, main (HEAD)

这意味着我们使用名称main 来查找提交H。如果我们现在运行git checkout develop,我们会得到:

...--G--H   <-- develop (HEAD), main

现在我们使用名称develop 来查找提交H。无论哪种方式,我们都已签出提交 H:可用文件从 H 复制,并且建议的下一个提交与提交 H 匹配。

现在我们将在工作树中创建一个新文件,修改一个现有文件,然后对两个文件运行git add。添加步骤会将新文件复制到暂存区,并将更新后的文件复制到暂存区,并将所有其他文件单独留在暂存区:现在暂存区中的所有文件都与我们工作树中的所有文件匹配。

现在我们运行git commit。 Git 将所有内容(包括它在暂存区域中知道的所有文件)打包到一个新的提交中,我们称之为I。新提交I父级 将是现有提交H,直到新提交I 成为当前提交为止。最后,Git 会将I 的哈希 ID 写入 当前名称develop,我们的 HEAD 附加到该名称:

...--G--H   <-- main
         \
          I   <-- develop (HEAD)

如果我们在develop 上进行另一个新的提交,我们会得到:

...--G--H   <-- main
         \
          I--J   <-- develop (HEAD)

这就是树枝的生长方式。

如果我们决定创建另一个新分支,我们现在有 两个“最后一次提交”候选者:提交 H,这是最近在 main 上的,或提交 J,这是develop 上的最新消息。你应该使用哪个?这取决于你。您可以选择这些 任何 其他现有提交中的任何一个。让我们选择提交H,以便我们的新分支feature 扩展自提交H

          I--J   <-- develop
         /
...--G--H   <-- feature (HEAD), main

现在我们可以像往常一样再进行两次提交:它们将从提交 H 中的文件开始,因为这是我们现在已经完成的提交,但是随着我们的进行,新快照中的一些文件会有所不同。让我们绘制结果提交:

          I--J   <-- develop
         /
...--G--H   <-- main
         \
          K--L   <-- feature (HEAD)

注意通过H 的提交是如何在所有三个 分支上进行的。提交I-J 只能通过从develop 开始并向后工作才能到达,而提交K-L 只能通过从feature 开始并向后工作才能到达,但我们可以使用名称main 来提交H ,或者从其他两个分支端中的任何一个开始(Git 将这些称为每个分支的 tip commit)并向后工作。

推送、获取等,第 1 部分:git fetch

正如我们刚刚看到的,分支名称可以帮助您和 Gitfind 提交。事实上,重要的不是名称,而是提交本身。不过,名称是我们查找提交的方式,因此名称并非完全不相关。

但是现在,我们注意到我们拥有不止一个 Git 存储库。我们需要一种在它们之间转移事物的方法。这就是git pushgit fetch 的用武之地。

注意git pull首先运行git fetch,然后运行第二个Git命令。很多人喜欢这样,因为获取步骤本身并不是很有用。有些人(包括我)不喜欢它,因为第二个命令的选择是有问题的。 Git 最近开始建议您明确配置第二个命令,或者明确选择它,而不是让 Git 为您选择一个。这是一个改进,但我认为最好先学会使用git fetch——它与 Git 与git push 的对立面一样接近——首先,然后学会使用第二个命令,只有 then 决定是否允许 Git 将它们作为一个单元运行。但这完全是另一个讨论。

git fetch 所做的是让您的 Git 调用另一个 Git。其他 Git 有提交,并且有帮助 Git find 提交的分支名称。您的 Git 需要 提交,因为这些才是最重要的。

假设你自己的 Git 有这些提交:

             J   <-- feature
            /
...--G--H--I   <-- main (HEAD)

假设 他们的 Git 有这些提交:

...--G--H--K   <-- main (HEAD)

你的 Git 调用他们的 Git。他们有一个提交K,而你根本没有。你可能想获得这个新的提交Kgit fetch 命令将执行此操作,将提交 K(他们唯一拥有但您没有的提交)存入您的存储库:

             J   <-- feature
            /
...--G--H--I   <-- main (HEAD)
         \
          K

请注意,您有 I-J 而他们没有,您有分支名称 feature 而他们没有。但是他们提交了K 而你没有提交,直到你的git fetch 得到它。但是您将如何找到提交K?请记住,它有一个看似随机的哈希 ID。

好吧,他们的Git 用名为master他们的 分支找到了提交。你的 Git 可以更新你的master,这样你就有了:

...--G--H--I--J   <-- feature
         \
          K   <-- main (HEAD)

但如果您的 Git 这样做了,您将不再在 您的 master 上提交 I。您仍然可以通过feature 访问它,但是如果您没有提交J 和名称feature 怎么办? (想一想,然后画出提交图的样子。)

您的 Git 所做的是更新您的分支名称中的任何。相反,您的 Git 会创建或更新一些 不是分支名称的名称。我称这些为远程跟踪名称。1您的 Git 通过在分支前面添加 origin/ 或其他名称2 形成这些名称名称:

             J   <-- feature
            /
...--G--H--I   <-- main (HEAD)
         \
          K   <-- origin/main

现在很容易找到提交K:他们称之为main,所以你的Git 称之为origin/main


1Git 调用这些远程跟踪分支名称,但这里的“分支”一词只是混淆了这个术语。它们不是分支名称。它们是分支名称创建的,但您不能将HEAD 附加到其中之一。

2卡在前面的是remote,加上斜线。这只是您用来让您的 Git 与其他 Git 对话的名称。 origin 是标准的第一个远程名称——git clone 默认使用origin——所以origin/ 非常常见,但您可以根据需要选择其他名称。


推送、获取等,第 2 部分:使用 git merge

这很常见,在许多设置中,您可以在自己的 Git 中拥有:

...--G--H   <-- main (HEAD), origin/main

当你听到(通过 Slack 或其他方式)某个同事创建了一个新的提交 I,你可以通过 git fetch 获得:

...--G--H   <-- main (HEAD)
         \
          I   <-- origin/main

此时,您几乎可以肯定只是想让您的 Git 推进您的 main,以便它选择提交 I。您还希望让您的 Git check out 提交 I,以便在工作树中(并准备好在暂存区域中的下一次提交)中的所有 文件来自 提交I。为此,您可以让 Git 执行 Git 所谓的快进合并,正如我们之前提到的。

我们可以画出这样的结果:

...--G--H--I   <-- main (HEAD), origin/main

您现在通过main“提交”I;您的工作树和索引已更新以匹配提交 I

这种 fetch-and-then-merge 操作非常常见 - 如此普遍,以至于 git pull 的存在就是为了做到这一点。运行git pullgit pull origin 让您的Git 调用origin 的另一个Git,获取任何新的提交,然后使用git merge 将您当前的分支(过去现在和现在仍然是main)与他们的@ 合并987654480@对口。您可以将git pull 设置为不同的操作,但我们不会在这里详细介绍,因为这已经很长了。

推送、获取等,第 3 部分:使用 git push

假设我们刚刚完成了上述操作,因此我们现在有提交 I 并与 origin 上的 Git 同步。现在我们修改工作树中的一些文件,或者添加一个新文件,然后进行新的提交J

             J   <-- main (HEAD)
            /
...--G--H--I   <-- origin/main

我们现在想做的是向另一个 Git 发送我们的提交 Jgit push 命令实现了这一点。我们运行:

git push origin main

origin 部分告诉 Git 呼叫谁:另一个 Git 在 originmain 部分告诉 Git 我们要发送哪个提交,再加上一件事,我们稍后再讨论。

我们的 Git 现在与他们的 Git 对话。我们的 Git 会找出哪个提交是他们的 main 的提示——仍然是提交 I——因此我们的 Git 发现我们唯一拥有但他们没有的 new 提交是提交J。因此,我们的 Git 向他们发送just commit J。如果他们打开了,比如说,提交 G,我们的 Git 会在此时向他们发送提交 H-I-J3 但我们只是发送他们 J

向他们发送我们的提交后,我们的 Git 会向他们的 Git 发送一个礼貌的请求:如果可以,请将您的 main 设置为指向提交 J 我们发送的名称这里,main,来自我们的git push。我们在此处发送的哈希 ID(用于提交 J)来自我们的 git push。请注意,我们不要求他们设置fred/maingeorge/main 或任何东西:我们要求他们设置他们的 main。如果他们接受这个请求,他们的分支就会更新。

因为这一个礼貌的请求,而不是一个强制的命令,他们可以并且会在各种情况下拒绝我们的尝试。 (即使使用强制命令,您也可以让 GitHub 拒绝某些命令。)不过,我们不会担心这里的任何命令。请注意git fetch(更新我们的远程跟踪名称)和git push(更新他们的分支)之间的显着差异名字。因此,虽然 fetch 和 push 接近于对立面,但在 Git 中,它们并不是严格对立的。

不过,在这种情况下,他们只会接受我们的请求。我们说 *update your main* 并且我们的提交添加到他们的提交中,所以他们说“好的”,当他们完成后,我们我们的存储库中,这个:

...--G--H--I--J   <-- main (HEAD), origin/main

我们的 Git 更新了我们自己的 origin/main,因为我们看到他们同意我们设置他们的 main 的请求。所以我们的origin/main,它记得他们的main 是我们最后一次与他们谈论它时的内容,应该更新。


3请注意,有人可能从另一个 Git 中删除提交 HI。在这种情况下,我们会将它们放回。这是很难摆脱提交的主要原因之一:您可以从您的 Git 存储库中删除它,但任何其他获得它的 Git 存储库可以还给你。这就像感染了一种病毒,您永远无法避免再次感染它。


在另一个 Git 上创建新分支

假设我们有:

...--G--H   <-- main (HEAD), origin/main

我们创建一个新分支——featureanother 或其他任何东西——然后使用git checkoutgit switch 进入它并以通常的方式向它添加一些提交:

...--G--H   <-- main, origin/main
         \
          I--J   <-- another (HEAD)

我们现在可以运行了:

git push origin another

这将调用他们的 Git,找出我们的哪些提交 I-J 是新的(当然,它们都是),发送给他们,然后礼貌地要求他们创建或更新他们的名字 another 到指向提交J

由于他们还没有这个名字,他们将通过创建名字another,指向提交J来服从这个请求。我们的 Git 将更新我们自己的存储库以添加新的远程跟踪名称:

...--G--H   <-- main, origin/main
         \
          I--J   <-- another (HEAD), origin/another

仅此而已。嗯,几乎所有。

上游

我们自己的每个分支名称都可以——不是必需的,但 可以——具有 Git 所谓的 upstream 之一。上游只是另一个名称。出于我们的目的,我们希望这个名称成为我们的远程跟踪名称之一。

我们的main 可能已经将origin/main 设置为上游。我们刚刚创建了another,当我们这样做时,我们没有设置任何上游,所以它根本没有上游。我们的git push 没有设置上游。因此,如果我们希望将 origin/another 设置为我们名为 another 的分支的上游,我们现在必须这样做:

git branch --set-upstream-to=origin/another another

这只是将我们名为 another 的分支的上游设置为 origin/another4 我们必须在我们的 git push 之后执行此操作,因为我们没有在那之前有origin/another。我们的origin/another 现在存在因为名为another 的分支现在存在于origin

为了使这更容易一点,我们可以运行git push -u origin another。这个-ugit push 成功后为我运行git branch --set-upstream-to 的缩写

一旦我们有了这个上游集,我们就可以运行以下任何命令:

git fetch
git merge
git pull
git push

当我们在名为feature 的分支上时发生“正确的事情”,其中git fetch 的“正确的事情”是调用origingit merge 的“正确的事情”是与origin/another 合并,以此类推。拥有上游集也使git status 输出具有更多信息。


4在相当老的 Git 版本中,没有 --set-upstream-to 选项,你必须使用 --set-upstream,它的参数有点倒退,这会造成混乱,这就是为什么 Git 现在有--set-upstream-to。如果你有这么旧的 Git,请升级它。如果无法升级,请记住此脚注。


如果您已经在origin 上创建了another 怎么办?

在您的案例中,您使用 GitHub 的 Web 界面在 origin 上创建了一个名为 another 的分支。您现在可以运行:

git fetch origin

这会让你的 Git 调用他们的 Git,列出他们的分支名称,确定他们是否有任何新的提交(也许有,也许没有),并且使用他们列出的分支名称来创建或更新您自己的远程跟踪名称。

假设您从顶部开始,然后:

...--G--H   <-- main (HEAD), origin/main

然后,您使用 GitHub 的界面在那边的main 上创建一个或两个新提交,然后运行git pull,它首先在您的机器上运行git fetch,以获得:

...--G--H   <-- main (HEAD)
         \
          I--J   <-- origin/main

您的git pull 现在运行git merge,它使用您的main 的上游设置与您刚刚更新的origin/main / 提交J 合并,它执行了快进而不是合并:

...--G--H--I--J   <-- main (HEAD), origin/main

然后您使用 GitHub 在 那个 存储库上创建名称 another,在 GitHub 上指向提交 J

如果您再运行一次 git fetch,您的 Git 将看到它们的名称为 another,并将创建您自己的 origin/another,如下所示:

...--G--H--I--J   <-- main (HEAD), origin/another, origin/main

你现在可以运行了:

git checkout another

在您自己的 Git 存储库中。这会四处寻找一个名为another 的分支,但没有找到。不过,您的 Git 不会立即给您一个错误,而是会继续查找,这次是所有远程跟踪名称,并找到 origin/another。此名称是在您刚刚请求的名称前面粘贴origin/ 的结果。所以你的 Git 现在使用 Git 所谓的 Do What I MeanDWIM 模式来创建一个新的(本地)分支名称,another,指向与您的origin/another 相同的提交,仍然是提交J,然后执行该名称的git checkout

...--G--H--I--J   <-- another (HEAD), main, origin/another, origin/main

这种分支创建为新分支设置上游:新创建的名称another的上游是origin/another5

您现在可以在这个名为 another 的分支上进行新的提交,然后运行:

git push

因为它的上游是origin/another,所以这个git push 会知道推送到origin 并要求他们根据你命名的another 使用他们的名字another


5这一切都非常可配置,但根据我的观察,我从未见过有人关闭此特定选项。

【讨论】:

    【解决方案2】:

    先移到分支another

    git checkout another
    

    然后从另一个分支(本例中为master)复制文件

    git checkout master -- path/to/file
    

    然后推送到another 分支

    git add *
    git commit -m "message"
    git push origin another
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-10
      • 2011-11-24
      • 1970-01-01
      • 2014-05-17
      • 1970-01-01
      • 1970-01-01
      • 2021-04-10
      • 1970-01-01
      相关资源
      最近更新 更多