创建一个只包含一些未提交更改的新分支
Create a new branch containing only some of the uncommitted changes
我正在尝试创建一个新分支,同时使用一些未提交的更改,是否可以只将一些更改导入到新分支?
其中一些命令有点不透明(其中两个是破坏性的)。如果您不确定它们的作用,那么在尝试它们之前先阅读这一部分可能会有所帮助。
git add some_file some_directory
git add 可以只按文件或目录添加(其实常见的git add .
只是将当前目录添加为目录)。
git commit -m "temp"
创建一个我们将丢弃的临时提交。
git add .
添加其他所有内容,以便我们可以将其存放干净。
git stash
将当前工作状态存储在缓冲区中(稍后我们将把它取回来)。
git checkout -b some-branch
将当前工作树(包括“临时”提交)带到您想要的新分支。
git checkout -
“-”是返回到您所在的上一个分支的快捷方式。
git rev-parse HEAD^^ | pbcopy
这是一堆。 . .最好单独尝试。
git rev-parse HEAD^ 将 return 当前分支的最新提交 sha。
添加第二个 ^ 如 HEAD^^ 将引用第二个。 . .等等
管道到 pbcopy(MacOS 上的系统剪贴板)只是为了方便。您可以省略这部分,只突出显示终端的输出。
git reset --hard <some_sha>
:警告:破坏性行为:警告:
这将使当前工作状态恢复到所需的提交状态(删除所有其他内容)。
在这种情况下,我们将其他所有内容都存储在一个存储区中,所以没问题。
git stash pop
带回我们存放在藏匿处的东西。
这是 Mac 上的一种方法(尽管非常手动):
git add <file(s)>
git commit -m "temp"
git add .
git stash
git checkout -b new-branch
git checkout -
git rev-parse HEAD^^ | pbcopy
git reset --hard <paste from clipboard>
git stash pop
这让您处于的最终状态:
您现在有一个分支“new-branch”,您的代码更改在“临时”提交下提交。
并且您的主分支没有新分支中的更改。
如果我决定我在分支上所做的工作不是我正在开发的功能的方向,我将使用类似这样的流程。 . .但也许我不想完全删除代码(以防万一我以后想要它)。
是根本没有任何“未提交的更改”。虽然 git status
报告 “为提交准备的更改”和“未为提交准备的更改”,但这份报告本身就是一种谎言,旨在 Git更有用。 (是否真的做到这一点完全是另一个问题。)
这最终意味着解决方案很简单:只需创建新分支,添加您喜欢的任何内容,然后 运行 git commit
。 (不要将 -a
选项用于 git commit
。)
龙:为什么这是答案
理解所有这些的关键部分是:
- Git 都是关于提交的。
- 提交包含 快照,不包含差异。
- Git 从 提议的快照 进行新提交,它位于 Git 不同的调用中, index、暂存区,以及(现在很少)缓存。这三个名字都是为了一个东西。
- 您在工作树中可以查看和使用的文件与 Git 索引中的副本1 是分开的。
最后一点的意思是,当您处理提交时,实际上每个文件“活动”有 三个 个副本:
一个副本严格 read-only,存储在 当前提交 中。你可以让 Git 读出来,但没有任何东西——甚至 Git 本身——都不能改变它。
第二个副本(见脚注 1)位于 Git 的索引中。
您在工作树中看到和使用的副本实际上并未被 Git 本身使用(但见下文)。 Git 将在 git checkout
上将提交的文件复制到 Git 的索引和您的工作树。如果您更改了工作树副本,新的 git commit
将不会使用更新后的文件。相反,它将提交 Git 索引中的副本。
这就是为什么您在提交之前总是必须 git add
文件。2 git add
步骤告诉 Git:将此文件的 work-tree 副本复制到您的索引副本中。 作为此复制过程的一部分,Git 实际上准备了文件的 to-be-committed 版本(位于一种特殊的 Git-ized、read-only 格式和 pre-de-duplicated 以便所有使用相同内容的提交,只需 re-use 准备好的文件)。
换句话说,索引中的内容在任何时间点都是将在您将进行的下一个 提交中的文件集。这直接导致 git status
如何告诉你它告诉你什么:
首先,git status
打印 on branch master
之类的内容,以及其他有用的内容,我们将在此处忽略。
接下来,对于当前提交中的每个文件,Git将该文件与索引中的文件进行比较。如果两者相同,Git 就什么都不说了。如果它们不同,Git 表示该文件 暂存用于提交 。如果文件在索引中丢失,Git 表示它已被删除,如果索引中的文件不在提交中,Git 表示它是一个新文件。但无论哪种方式,这都是“暂存提交”因为索引包含建议的下一次提交。索引中的某些内容与当前提交中的内容相匹配,因此 Git 只是没有提及。
第三,对于 index 中的每个文件,Git 将该文件与工作树中的文件进行比较。如果两者相同,Git 就什么都不说了。如果它们不同,Git 表示该文件未暂存提交。如果文件丢失,Git 表示它已被删除。
最后,对于工作树中但不在索引中的文件有一个特殊情况。这些是您的未跟踪文件。 Git 分别列出它们。 (如果它们列在 .gitignore
中,Git 会禁止有关文件未被跟踪的投诉。)
所以每当Git说到变化,不管是staged还是unstaged,真正的意思是文件的各个副本是不同的。提交中的副本实际上无法更改:它们一直被冻结,或者至少,只要提交本身存在。索引中的副本处于冻结状态,de-duplicated 格式 但是 可以 通过覆盖它们来更改;这就是 git add
所做的。你的工作树中的副本是你的,你可以随心所欲地处理。除了 git add
将它们复制到 Git 的索引中,以及 git checkout
用来自某个提交的文件替换你的工作树文件,工作树是你的,你可以随意使用。
1从技术上讲,该索引包含对 de-duplicated 内部 Git [=139= 的 引用 ]blob 对象,而不是 t一个文件的副本。提交也在内部使用这些 blob 对象——这就是为什么文件的索引“副本”已准备好提交。
2当您使用 git commit -a
时,Git 基本上会为您执行所有文件的 git add
。这里的 essentially 一词涵盖了 Git 执行此操作的方式,即使用额外的临时索引,以防提交本身失败。这个额外的索引允许 Git 为特定情况“撤消”添加。如果使用 git commit --only
,事情会变得更加复杂:此时 Git 生成 两个 临时索引文件。我们将在这里忽略这种情况。
请注意 git commit -a
中的 git add
大致等同于 git add -u
:它仅对 Git 已包含在其索引中的文件进行操作。如果您有一个全新的文件,但 Git 的索引中尚不存在,您仍然必须手动 git add
那个文件。
关于分支名称的知识
我在上面提到,提交包含快照,正如我们刚刚看到的,快照是根据您 运行 [=17] 时 Git 索引中的文件副本构建的=].但是关于每个提交还有更多要了解的。每个都有编号,带有 random-looking(但实际上根本不是随机的)哈希 ID。 Git 在内部使用这些数字从其“所有内部散列对象”的大数据库中读取实际提交数据。
因此,要找到一个提交,Git需要它的哈希ID。但是每个提交本身都包含其前一个提交的哈希 ID,Git 调用提交的 parent。这意味着如果我们有很长的提交链,我们可以这样画:
... <-F <-G <-H
其中每个大写字母代表一个丑陋的大哈希 ID。这里链中的 last 提交是提交 H
,在提交 H
内,Git 有每个文件的完整快照,plus 关于提交本身的一些信息:例如,谁在什么时候提交的,以及 immediately-previous 提交的哈希 ID G
.
通过读取提交H
,然后,Git可以得到更早的提交G
的ID。将 G
中的快照与 H
中的快照进行比较,Git 向您展示了这两个提交之间 发生了什么变化。同样重要的是,这让 Git 回到过去,因为提交 G
包含较早提交 F
的哈希 ID。 F
依次包含 even-earlier 提交的哈希 ID,依此类推。
这里棘手的部分是 Git 必须以某种方式找到提交 H
。这就是 分支名称 的用武之地。分支名称只包含链中 last 提交的哈希 ID。所以我们最终得到:
...--F--G--H <-- master
名称 master
让 Git 轻松找到提交 H
。该提交让 Git 找到每个较早的提交。在 Git 中,历史就是这样运作的:一切都是 向后 。我们只是从头开始,然后根据需要向后工作。
要创建新的分支名称,您必须选择一些现有的提交。通常,您将从 当前提交 开始,这是 当前分支 上的最后一次提交。 git branch
或 git checkout -b
命令将创建新的分支名称,以便它 select 与相同的提交:
...--F--G--H <-- master, newbranch
现在我们还需要一件事,这是一种方法来判断我们正在使用这两个 names 中的哪一个 - git status
会在它说 在分支 <em>B</em>
,对于某些 B
。所以我们将特殊名称 HEAD
附加到一个分支名称。如果我们在分支 master
上,我们有:
...--F--G--H <-- master (HEAD), newbranch
git checkout
还有一招
请注意,两个名称 select 相同的提交!当我们 git checkout newbranch
时,我们得到:
...--F--G--H <-- master, newbranch (HEAD)
但我们仍在使用提交 H
。这仍然是当前提交,并且由于我们没有更改提交,git checkout
不会触及Git 的索引或我们的工作树。这意味着我们可以创建一个新分支并切换到它,就像 git checkout -b
一样,而不会弄乱任何其他状态。
现在我们有了这个新分支并正在使用它,现在我们可以使用git add
(甚至git add -p
)到select 主动更新 Git 索引中的特定文件。当我们 运行 git commit
, Git 将打包其索引并进行新的提交 I
:
...--F--G--H <-- master
\
I <-- newbranch (HEAD)
一旦 Git 做出这个新提交(从当时 Git 的索引中的任何内容),Git 将新提交的哈希 ID 写入当前分支,即 newbranch
,因为 HEAD
附加到 newbranch
。所以现在这个名字标识了分支上的最新提交。
(如果我们愿意,现在通常可以切换回 master
。这里的关键是通过将未暂存的文件写入 Git 的索引来使它们暂存,然后写入索引进入提交,所有三个 those 文件匹配。只有文件 won' 需要在索引副本和 work-tree 副本不同的地方进行替换。有关这方面的更多详细信息,请参阅 Checkout another branch when there are uncommitted changes on the current branch。)
我正在尝试创建一个新分支,同时使用一些未提交的更改,是否可以只将一些更改导入到新分支?
其中一些命令有点不透明(其中两个是破坏性的)。如果您不确定它们的作用,那么在尝试它们之前先阅读这一部分可能会有所帮助。
git add some_file some_directory
git add 可以只按文件或目录添加(其实常见的git add .
只是将当前目录添加为目录)。
git commit -m "temp"
创建一个我们将丢弃的临时提交。
git add .
添加其他所有内容,以便我们可以将其存放干净。
git stash
将当前工作状态存储在缓冲区中(稍后我们将把它取回来)。
git checkout -b some-branch
将当前工作树(包括“临时”提交)带到您想要的新分支。
git checkout -
“-”是返回到您所在的上一个分支的快捷方式。
git rev-parse HEAD^^ | pbcopy
这是一堆。 . .最好单独尝试。
git rev-parse HEAD^ 将 return 当前分支的最新提交 sha。
添加第二个 ^ 如 HEAD^^ 将引用第二个。 . .等等
管道到 pbcopy(MacOS 上的系统剪贴板)只是为了方便。您可以省略这部分,只突出显示终端的输出。
git reset --hard <some_sha>
:警告:破坏性行为:警告:
这将使当前工作状态恢复到所需的提交状态(删除所有其他内容)。
在这种情况下,我们将其他所有内容都存储在一个存储区中,所以没问题。
git stash pop
带回我们存放在藏匿处的东西。
这是 Mac 上的一种方法(尽管非常手动):
git add <file(s)>
git commit -m "temp"
git add .
git stash
git checkout -b new-branch
git checkout -
git rev-parse HEAD^^ | pbcopy
git reset --hard <paste from clipboard>
git stash pop
这让您处于的最终状态:
您现在有一个分支“new-branch”,您的代码更改在“临时”提交下提交。
并且您的主分支没有新分支中的更改。
如果我决定我在分支上所做的工作不是我正在开发的功能的方向,我将使用类似这样的流程。 . .但也许我不想完全删除代码(以防万一我以后想要它)。
git status
报告 “为提交准备的更改”和“未为提交准备的更改”,但这份报告本身就是一种谎言,旨在 Git更有用。 (是否真的做到这一点完全是另一个问题。)
这最终意味着解决方案很简单:只需创建新分支,添加您喜欢的任何内容,然后 运行 git commit
。 (不要将 -a
选项用于 git commit
。)
龙:为什么这是答案
理解所有这些的关键部分是:
- Git 都是关于提交的。
- 提交包含 快照,不包含差异。
- Git 从 提议的快照 进行新提交,它位于 Git 不同的调用中, index、暂存区,以及(现在很少)缓存。这三个名字都是为了一个东西。
- 您在工作树中可以查看和使用的文件与 Git 索引中的副本1 是分开的。
最后一点的意思是,当您处理提交时,实际上每个文件“活动”有 三个 个副本:
一个副本严格 read-only,存储在 当前提交 中。你可以让 Git 读出来,但没有任何东西——甚至 Git 本身——都不能改变它。
第二个副本(见脚注 1)位于 Git 的索引中。
您在工作树中看到和使用的副本实际上并未被 Git 本身使用(但见下文)。 Git 将在
git checkout
上将提交的文件复制到 Git 的索引和您的工作树。如果您更改了工作树副本,新的git commit
将不会使用更新后的文件。相反,它将提交 Git 索引中的副本。
这就是为什么您在提交之前总是必须 git add
文件。2 git add
步骤告诉 Git:将此文件的 work-tree 副本复制到您的索引副本中。 作为此复制过程的一部分,Git 实际上准备了文件的 to-be-committed 版本(位于一种特殊的 Git-ized、read-only 格式和 pre-de-duplicated 以便所有使用相同内容的提交,只需 re-use 准备好的文件)。
换句话说,索引中的内容在任何时间点都是将在您将进行的下一个 提交中的文件集。这直接导致 git status
如何告诉你它告诉你什么:
首先,
git status
打印on branch master
之类的内容,以及其他有用的内容,我们将在此处忽略。接下来,对于当前提交中的每个文件,Git将该文件与索引中的文件进行比较。如果两者相同,Git 就什么都不说了。如果它们不同,Git 表示该文件 暂存用于提交 。如果文件在索引中丢失,Git 表示它已被删除,如果索引中的文件不在提交中,Git 表示它是一个新文件。但无论哪种方式,这都是“暂存提交”因为索引包含建议的下一次提交。索引中的某些内容与当前提交中的内容相匹配,因此 Git 只是没有提及。
第三,对于 index 中的每个文件,Git 将该文件与工作树中的文件进行比较。如果两者相同,Git 就什么都不说了。如果它们不同,Git 表示该文件未暂存提交。如果文件丢失,Git 表示它已被删除。
最后,对于工作树中但不在索引中的文件有一个特殊情况。这些是您的未跟踪文件。 Git 分别列出它们。 (如果它们列在
.gitignore
中,Git 会禁止有关文件未被跟踪的投诉。)
所以每当Git说到变化,不管是staged还是unstaged,真正的意思是文件的各个副本是不同的。提交中的副本实际上无法更改:它们一直被冻结,或者至少,只要提交本身存在。索引中的副本处于冻结状态,de-duplicated 格式 但是 可以 通过覆盖它们来更改;这就是 git add
所做的。你的工作树中的副本是你的,你可以随心所欲地处理。除了 git add
将它们复制到 Git 的索引中,以及 git checkout
用来自某个提交的文件替换你的工作树文件,工作树是你的,你可以随意使用。
1从技术上讲,该索引包含对 de-duplicated 内部 Git [=139= 的 引用 ]blob 对象,而不是 t一个文件的副本。提交也在内部使用这些 blob 对象——这就是为什么文件的索引“副本”已准备好提交。
2当您使用 git commit -a
时,Git 基本上会为您执行所有文件的 git add
。这里的 essentially 一词涵盖了 Git 执行此操作的方式,即使用额外的临时索引,以防提交本身失败。这个额外的索引允许 Git 为特定情况“撤消”添加。如果使用 git commit --only
,事情会变得更加复杂:此时 Git 生成 两个 临时索引文件。我们将在这里忽略这种情况。
请注意 git commit -a
中的 git add
大致等同于 git add -u
:它仅对 Git 已包含在其索引中的文件进行操作。如果您有一个全新的文件,但 Git 的索引中尚不存在,您仍然必须手动 git add
那个文件。
关于分支名称的知识
我在上面提到,提交包含快照,正如我们刚刚看到的,快照是根据您 运行 [=17] 时 Git 索引中的文件副本构建的=].但是关于每个提交还有更多要了解的。每个都有编号,带有 random-looking(但实际上根本不是随机的)哈希 ID。 Git 在内部使用这些数字从其“所有内部散列对象”的大数据库中读取实际提交数据。
因此,要找到一个提交,Git需要它的哈希ID。但是每个提交本身都包含其前一个提交的哈希 ID,Git 调用提交的 parent。这意味着如果我们有很长的提交链,我们可以这样画:
... <-F <-G <-H
其中每个大写字母代表一个丑陋的大哈希 ID。这里链中的 last 提交是提交 H
,在提交 H
内,Git 有每个文件的完整快照,plus 关于提交本身的一些信息:例如,谁在什么时候提交的,以及 immediately-previous 提交的哈希 ID G
.
通过读取提交H
,然后,Git可以得到更早的提交G
的ID。将 G
中的快照与 H
中的快照进行比较,Git 向您展示了这两个提交之间 发生了什么变化。同样重要的是,这让 Git 回到过去,因为提交 G
包含较早提交 F
的哈希 ID。 F
依次包含 even-earlier 提交的哈希 ID,依此类推。
这里棘手的部分是 Git 必须以某种方式找到提交 H
。这就是 分支名称 的用武之地。分支名称只包含链中 last 提交的哈希 ID。所以我们最终得到:
...--F--G--H <-- master
名称 master
让 Git 轻松找到提交 H
。该提交让 Git 找到每个较早的提交。在 Git 中,历史就是这样运作的:一切都是 向后 。我们只是从头开始,然后根据需要向后工作。
要创建新的分支名称,您必须选择一些现有的提交。通常,您将从 当前提交 开始,这是 当前分支 上的最后一次提交。 git branch
或 git checkout -b
命令将创建新的分支名称,以便它 select 与相同的提交:
...--F--G--H <-- master, newbranch
现在我们还需要一件事,这是一种方法来判断我们正在使用这两个 names 中的哪一个 - git status
会在它说 在分支 <em>B</em>
,对于某些 B
。所以我们将特殊名称 HEAD
附加到一个分支名称。如果我们在分支 master
上,我们有:
...--F--G--H <-- master (HEAD), newbranch
git checkout
还有一招
请注意,两个名称 select 相同的提交!当我们 git checkout newbranch
时,我们得到:
...--F--G--H <-- master, newbranch (HEAD)
但我们仍在使用提交 H
。这仍然是当前提交,并且由于我们没有更改提交,git checkout
不会触及Git 的索引或我们的工作树。这意味着我们可以创建一个新分支并切换到它,就像 git checkout -b
一样,而不会弄乱任何其他状态。
现在我们有了这个新分支并正在使用它,现在我们可以使用git add
(甚至git add -p
)到select 主动更新 Git 索引中的特定文件。当我们 运行 git commit
, Git 将打包其索引并进行新的提交 I
:
...--F--G--H <-- master
\
I <-- newbranch (HEAD)
一旦 Git 做出这个新提交(从当时 Git 的索引中的任何内容),Git 将新提交的哈希 ID 写入当前分支,即 newbranch
,因为 HEAD
附加到 newbranch
。所以现在这个名字标识了分支上的最新提交。
(如果我们愿意,现在通常可以切换回 master
。这里的关键是通过将未暂存的文件写入 Git 的索引来使它们暂存,然后写入索引进入提交,所有三个 those 文件匹配。只有文件 won' 需要在索引副本和 work-tree 副本不同的地方进行替换。有关这方面的更多详细信息,请参阅 Checkout another branch when there are uncommitted changes on the current branch。)