我可以从主分支创建分支,而不将主分支上的任何内容复制到新分支吗?我可以这样做吗?git命令可以做什么?
1dkrff031#
不,但问题(“从主人那里创造…”)迫使回答;我们必须以一种特殊的方式来解释“from”,这会迫使“no”。有了正确的问题,答案就会变成“是”。请参阅Creating a new empty branch for a new project或阅读下面的详细答案。
分支不是从分支创建的。它们是由于和/或指向提交而创建的。更具体地说,branchname只是指向单个提交的指针,Git称之为tip-commit*。提交本身也可以用作指针:每个提交都指向其父级或父级。也就是说,我们有一些系列的提交:
... <-F <-G <-H
以哈希ID为H的最近一次提交结束。提交H指向其父G,后者指向F等等。定位最后一次提交的分支名称仅指向m1n 5o1p本身:
H
G
F
...--F--G--H <-- master
创建新分支名称时,至少使用a-常用方法:
git branch <new-name> [<start-point>]
其中可选的*start-point参数指定哪个commit哈希ID将进入这个新的分支名称。如果省略start-point*,则进入新分支名称的哈希ID就是当前提交的哈希标识。如果当前提交是H,新名称为br时,我们得到:
start-point
br
...--F--G--H <-- br, master
例如,您只能使用git checkout master或git switch master“on”这些分支中的一个,因此Git将特殊名称HEAD附加到一个分支名称,以记住我们使用的分支名称:
git checkout master
git switch master
HEAD
...--F--G--H <-- br, master (HEAD)
因此,HEAD提供了两个独立问题的答案:
各种其他形式的“创建一个新分支”操作都做同样的事情:它们创建一个指向某个“现有提交”的新名称。现有的提交具有它拥有的任何文件。如果您使用默认的HEAD创建,或者如果您使用可选的起始点参数,则现有的提交已经在当前分支上。这种情况下的最终结果:
所有这些提交现在都在两个分支上,而不是只在一个分支上。当您在某个分支B上进行新提交时,新提交将获得一个新的、唯一的哈希ID
...--F--G--H <-- br (HEAD), master
然后进行一些更改并提交它们以生成新的提交I。新的提交的I具有所有文件的完整快照。提交不保存更改,它们保存快照,但更重要的是,提交的m2n20o1p的parent是现有的提交m2n21o1p:
I
...--F--G--H \ I
名称master不移动。我们不在master上;我们在br上。名称br*移动:
master
...--F--G--H <-- master \ I <-- br (HEAD)
直到H的提交继续在两个分支上进行,但新的提交I仅在br上进行。我们或Git,至少-findcommits,方法是取一个像br或master这样的名称,然后按照它的箭头,并得到一个I或者H之类的commit。从这个commit,Git可以遵循它的向后指向的箭头:如果我们在H并向后移动一步,我们就会到达G。这个commit也有一个父级,所以如果我们遵循这个箭头,我们最终会提交F,以此类推。如果我们让Git移动名称master,这将更改现有提交所在的分支集合。它不会更改任何现有的commits*,只会更改可以找到它们的名称。这正是Git的主要目的:我们创建新的提交,它保存快照并具有指向其父母的指针。然后我们让Git通过分支名称或哈希ID检查一些提交,然后我们得到旧快照。或者,我们有Git比较任何两个提交。如果我们比较父母和孩子,差异表明某人改变了。提交仍然是快照!就像说今天是20摄氏度,昨天是18摄氏度,所以差异是2摄氏度。我们可能会关心温差,或者实际温度。Git可以做任何一个,但它存储实际的东西,而不是差异。
现在,在一个全新的、完全空的存储库中显然存在一个问题。像master这样的分支名称必须指向某个现有的有效提交。但是这个新的、完全空的存储库没有提交。那么master可以指向哪个提交?Git通过说您在分支master上,但分支m1n 40o1p不存在来解决这个问题。没有提交,因此没有有效的哈希ID,因此分支不存在。这就可以了:它不存在,所以它不能识别任何现有提交的事实是好的。换句话说,在一个根本不存在的分支上运行是可以的,并且在任何新的、完全空的存储库中,都会自动获得该状态。然后,当您进行第一次提交时,Git会创建提交并同时创建分支名称:
A <-- master (HEAD)
现在,存储库中有一个提交,有一个很大的哈希ID,但我们在这里称它为A。namemaster现在存在,并指向现有的提交A。
A
您可以随时将Git放回这种情况:只需使用git checkout --orphan *new-branch*。Git会给你一个不存在的分支名称。git status将告诉您您在新的分支上,而git log则不会显示任何内容(有时会显示错误消息:Git作者最终将Git修复为智能,只需说“还没有提交”)。
git checkout --orphan *new-branch*
git status
git log
Git的新手经常认为Git使用他们的工作树文件。这是一个很大的挫折来源,因为这不是真的。Git会在你要求的时候填充你的工作树,但这不是它用来制作新提交的快照的。当您要求Git进行新提交时:
git commit
Git将Git的索引中的所有文件作为新快照写入。这个名字,索引,不是一个很好的名字,所以这个东西现在有了另一个名字:Git称它为暂存区域。1 Git索引中的文件是进入快照的文件。通常,索引中充满了文件。只是正常情况下,这些文件也与当前提交中的所有文件相匹配。也就是说,假设您正在提交H:
...--G--H <-- master (HEAD)
你是通过做git checkout master来的。这填充了Git的文件从提交H开始的索引,也填充了您的工作树-从提交m2n50o1p可以看到和使用的文件。2因此Git索引中的文件与HEAD提交中的文件匹配。这让Git说没有什么要做的。您可以使用git add将文件放入Git的索引中,它将从您的工作树复制到Git的目录。您可以使用git rm将文件从Git的索引中取出,这将删除您的工作树副本和Git的目录副本。如果您从Git的索引(和您的工作树)中删除了所有文件,那么索引现在真的是空的。如果您更改了任何工作树文件,您总是必须再次对其进行git add。原因很简单,很明显:Git没有使用工作树副本。为了让Git在下一次提交中放入更新的文件,您必须首先告诉Git将工作树副本复制回索引,替换现有的索引副本。31暂存区域通常是一个更好的名称,但它并没有涵盖索引实际所做的一切,所以我倾向于自己使用index。2从技术上讲,Git首先从提交复制到其索引,然后从其索引复制到您的工作树。在Git 2.23及其新的git restore命令之前,Git在内部总是以两步式的方式完成。3从技术上讲,索引实际上只保存文件的名称、模式和blob哈希ID。文件副本的实际数据存储为内部Git对象。但这对您来说都是不可见的,除非您开始使用git ls-files --stage和git update-index命令,这些命令实际上并不适用于正常工作。
git add
git rm
git restore
git ls-files --stage
git update-index
假设我们做两件事:1.git checkout --orphan new-branch1.git rm -r .距顶层命令#1将我们置于一个新的分支new-branch上,该分支不存在。这与完全空的存储库中的状态类似,当我们在master上时,master存在,但这次不存在的分支名称是new-branch。命令#2删除Git索引中的所有文件,并从工作树中删除这些相同的文件。这样可以消除工作树的杂乱,这样您就可以看到现在没有文件了。请注意,未跟踪文件不会被删除;要删除这些文件,请运行git clean -dfx(清除所有内容,包括空目录,并清除.gitignore-ed文件)。现在您有了一个干净的板岩:一个真正的空索引,没有文件。现在,您可以创建新文件并将其复制到Git的索引中。当您有一组自己喜欢的文件时,使用git commit创建一个无父级的新提交:
git checkout --orphan new-branch
git rm -r .
new-branch
git clean -dfx
.gitignore
...--G--H <-- master I <-- new-branch (HEAD)
新的提交I这次不会指向H。新提交的父级是当前提交的内容,git checkout --orphan安排为没有当前提交*。我们有一个当前分支,但该分支不存在,因此没有当前提交。请注意,如果在步骤1中省略--orphan,您将得到:
git checkout --orphan
--orphan
...--G--H <-- master \ I <-- new-branch (HEAD)
也就是说,您可以继续并使初始设置如下所示:
...--G--H <-- master, new-branch (HEAD)
然后,您可以使用git rm -r .删除所有文件,创建新文件,将它们添加到现在为空的索引中,然后提交,您将像往常一样使用父H获得新的提交。然而,Commit H将位于新的分支上,Git现在将比较Commit mn75o1p中的快照及其所有文件,以及新Commit m2n76o1p的快照及其独立文件,并告诉您将Commit m1 n77o1p转换为Commit m2 n78o1p需要删除所有这些文件。
换言之,这两种设置之间的区别在于,对于--orphan,新分支根本不连接到旧分支。新分支上的提交以新的根提交开始。这些历史不再在过去的某个时间点重新连接(提交H):它们是独立的、不相关的历史。在I之后添加的新提交继续与H提交无关:
...--G--H <-- master I--J--K <-- new-branch (HEAD)
您可以随时使用git checkout master选择现有的提交H和分支名称master。这将从Git的索引中删除当前提交m1n 86o1p中的文件,同时从工作树中删除这些文件,并将H的快照提取到Git的目录中,然后将这些文件复制到您的工作树中。
你需要知道的是:
git checkout
dw1jzc5e2#
您可以暂存一个空树,这是在提交时有效删除所有文件的快速方法。git read-tree --empty将索引设置为空树。提交此操作会留下历史记录,但会进行删除所有内容的提交。如果需要单独的清除历史记录,请参见为新项目创建新的空分支
git read-tree --empty
2条答案
按热度按时间1dkrff031#
TL;博士
不,但问题(“从主人那里创造…”)迫使回答;我们必须以一种特殊的方式来解释“from”,这会迫使“no”。有了正确的问题,答案就会变成“是”。请参阅Creating a new empty branch for a new project或阅读下面的详细答案。
长
分支不是从分支创建的。它们是由于和/或指向提交而创建的。
更具体地说,branchname只是指向单个提交的指针,Git称之为tip-commit*。提交本身也可以用作指针:每个提交都指向其父级或父级。
也就是说,我们有一些系列的提交:
以哈希ID为
H
的最近一次提交结束。提交H
指向其父G
,后者指向F
等等。定位最后一次提交的分支名称仅指向m1n 5o1p本身:创建新分支名称时,至少使用a-常用方法:
其中可选的*
start-point
参数指定哪个commit哈希ID将进入这个新的分支名称。如果省略start-point
*,则进入新分支名称的哈希ID就是当前提交的哈希标识。如果当前提交是H
,新名称为br
时,我们得到:例如,您只能使用
git checkout master
或git switch master
“on”这些分支中的一个,因此Git将特殊名称HEAD
附加到一个分支名称,以记住我们使用的分支名称:因此,
HEAD
提供了两个独立问题的答案:HEAD
以查看其连接位置。)HEAD
,然后读取其连接的分支名称。)各种其他形式的“创建一个新分支”操作都做同样的事情:它们创建一个指向某个“现有提交”的新名称。
现有的提交具有它拥有的任何文件。如果您使用默认的
HEAD
创建,或者如果您使用可选的起始点参数,则现有的提交已经在当前分支上。这种情况下的最终结果:所有这些提交现在都在两个分支上,而不是只在一个分支上。
当您在某个分支B上进行新提交时,新提交将获得一个新的、唯一的哈希ID
然后进行一些更改并提交它们以生成新的提交
I
。新的提交的I
具有所有文件的完整快照。提交不保存更改,它们保存快照,但更重要的是,提交的m2n20o1p的parent是现有的提交m2n21o1p:名称
master
不移动。我们不在master
上;我们在br
上。名称br
*移动:直到
H
的提交继续在两个分支上进行,但新的提交I
仅在br
上进行。我们或Git,至少-findcommits,方法是取一个像
br
或master
这样的名称,然后按照它的箭头,并得到一个I
或者H
之类的commit。从这个commit,Git可以遵循它的向后指向的箭头:如果我们在H
并向后移动一步,我们就会到达G
。这个commit也有一个父级,所以如果我们遵循这个箭头,我们最终会提交F
,以此类推。如果我们让Git移动名称
master
,这将更改现有提交所在的分支集合。它不会更改任何现有的commits*,只会更改可以找到它们的名称。这正是Git的主要目的:我们创建新的提交,它保存快照并具有指向其父母的指针。然后我们让Git通过分支名称或哈希ID检查一些提交,然后我们得到旧快照。或者,我们有Git比较任何两个提交。如果我们比较父母和孩子,差异表明某人改变了。提交仍然是快照!就像说今天是20摄氏度,昨天是18摄氏度,所以差异是2摄氏度。我们可能会关心温差,或者实际温度。Git可以做任何一个,但它存储实际的东西,而不是差异。
最初的空存储库困境
现在,在一个全新的、完全空的存储库中显然存在一个问题。像
master
这样的分支名称必须指向某个现有的有效提交。但是这个新的、完全空的存储库没有提交。那么master
可以指向哪个提交?Git通过说您在分支
master
上,但分支m1n 40o1p不存在来解决这个问题。没有提交,因此没有有效的哈希ID,因此分支不存在。这就可以了:它不存在,所以它不能识别任何现有提交的事实是好的。换句话说,在一个根本不存在的分支上运行是可以的,并且在任何新的、完全空的存储库中,都会自动获得该状态。然后,当您进行第一次提交时,Git会创建提交并同时创建分支名称:
现在,存储库中有一个提交,有一个很大的哈希ID,但我们在这里称它为
A
。namemaster
现在存在,并指向现有的提交A
。你可以随时重现这种困境
您可以随时将Git放回这种情况:只需使用
git checkout --orphan *new-branch*
。Git会给你一个不存在的分支名称。git status
将告诉您您在新的分支上,而git log
则不会显示任何内容(有时会显示错误消息:Git作者最终将Git修复为智能,只需说“还没有提交”)。Git从Git的索引进行新提交
Git的新手经常认为Git使用他们的工作树文件。这是一个很大的挫折来源,因为这不是真的。Git会在你要求的时候填充你的工作树,但这不是它用来制作新提交的快照的。
当您要求Git进行新提交时:
Git将Git的索引中的所有文件作为新快照写入。这个名字,索引,不是一个很好的名字,所以这个东西现在有了另一个名字:Git称它为暂存区域。1 Git索引中的文件是进入快照的文件。
通常,索引中充满了文件。只是正常情况下,这些文件也与当前提交中的所有文件相匹配。也就是说,假设您正在提交
H
:你是通过做
git checkout master
来的。这填充了Git的文件从提交H
开始的索引,也填充了您的工作树-从提交m2n50o1p可以看到和使用的文件。2因此Git索引中的文件与HEAD
提交中的文件匹配。这让Git说没有什么要做的。您可以使用
git add
将文件放入Git的索引中,它将从您的工作树复制到Git的目录。您可以使用git rm
将文件从Git的索引中取出,这将删除您的工作树副本和Git的目录副本。如果您从Git的索引(和您的工作树)中删除了所有文件,那么索引现在真的是空的。如果您更改了任何工作树文件,您总是必须再次对其进行
git add
。原因很简单,很明显:Git没有使用工作树副本。为了让Git在下一次提交中放入更新的文件,您必须首先告诉Git将工作树副本复制回索引,替换现有的索引副本。31暂存区域通常是一个更好的名称,但它并没有涵盖索引实际所做的一切,所以我倾向于自己使用index。
2从技术上讲,Git首先从提交复制到其索引,然后从其索引复制到您的工作树。在Git 2.23及其新的
git restore
命令之前,Git在内部总是以两步式的方式完成。3从技术上讲,索引实际上只保存文件的名称、模式和blob哈希ID。文件副本的实际数据存储为内部Git对象。但这对您来说都是不可见的,除非您开始使用
git ls-files --stage
和git update-index
命令,这些命令实际上并不适用于正常工作。这最终会给你所需的答案
假设我们做两件事:
1.
git checkout --orphan new-branch
1.
git rm -r .
距顶层命令#1将我们置于一个新的分支
new-branch
上,该分支不存在。这与完全空的存储库中的状态类似,当我们在master
上时,master
存在,但这次不存在的分支名称是new-branch
。命令#2删除Git索引中的所有文件,并从工作树中删除这些相同的文件。这样可以消除工作树的杂乱,这样您就可以看到现在没有文件了。请注意,未跟踪文件不会被删除;要删除这些文件,请运行
git clean -dfx
(清除所有内容,包括空目录,并清除.gitignore
-ed文件)。现在您有了一个干净的板岩:一个真正的空索引,没有文件。现在,您可以创建新文件并将其复制到Git的索引中。当您有一组自己喜欢的文件时,使用
git commit
创建一个无父级的新提交:新的提交
I
这次不会指向H
。新提交的父级是当前提交的内容,git checkout --orphan
安排为没有当前提交*。我们有一个当前分支,但该分支不存在,因此没有当前提交。请注意,如果在步骤1中省略
--orphan
,您将得到:也就是说,您可以继续并使初始设置如下所示:
然后,您可以使用
git rm -r .
删除所有文件,创建新文件,将它们添加到现在为空的索引中,然后提交,您将像往常一样使用父H
获得新的提交。然而,CommitH
将位于新的分支上,Git现在将比较Commit mn75o1p中的快照及其所有文件,以及新Commit m2n76o1p的快照及其独立文件,并告诉您将Commit m1 n77o1p转换为Commit m2 n78o1p需要删除所有这些文件。换言之,这两种设置之间的区别在于,对于
--orphan
,新分支根本不连接到旧分支。新分支上的提交以新的根提交开始。这些历史不再在过去的某个时间点重新连接(提交H
):它们是独立的、不相关的历史。在I
之后添加的新提交继续与H
提交无关:您可以随时使用
git checkout master
选择现有的提交H
和分支名称master
。这将从Git的索引中删除当前提交m1n 86o1p中的文件,同时从工作树中删除这些文件,并将H
的快照提取到Git的目录中,然后将这些文件复制到您的工作树中。结论
你需要知道的是:
git checkout
提交时,你的工作树会随之而来,因为Git索引中的文件是Git的内部Git专用格式,这只对Git有用,而对你或其他计算机软件不有用。您将处理工作树文件,然后使用git add
将它们复制回Git的索引,为下一次提交做好准备。git checkout --orphan
案例,加上最初的完全空的存储库,这很特别。当您提交时,这些将成为新的name。在那之前,你处于一种特殊的“未出生的分支”状态。dw1jzc5e2#
您可以暂存一个空树,这是在提交时有效删除所有文件的快速方法。
git read-tree --empty
将索引设置为空树。提交此操作会留下历史记录,但会进行删除所有内容的提交。如果需要单独的清除历史记录,请参见为新项目创建新的空分支