我正在主分支上工作,然后我决定创建一个名为test-branch
的新分支。我首先尝试git checkout feature
它警告我stash
或commit
我的更改。我用git stash save latest modification
stash
了它们,然后我用git checkout -b test-branch
去test-branch
。我试图添加所有文件(不忽略任何文件)并提交到分支。所以我从.gitignore
中删除了所有内容.跑完git add .
,我回到main
,没有承诺test-branch
。我使用git branch -D test-branch
删除了该分支。然后我在main
中使用了git stash apply
.现在我的代码已经转到了最后一个提交版本,并且在提交后所做的所有修改都不再存在。我现在该怎么办?
在切换树枝之前
PS C:UsersAdministratorDesktopprojectssongs> git checkout feature
error: Your local changes to the following files would be overwritten by checkout:
.gitignore
app.py
music.db
static/css/styles.css
templates/favorites.html
templates/layout.html
Please commit your changes or stash them before you switch branches.
PS C:UsersAdministratorDesktopprojectssongs> git stash save "latest modification"
Saved working directory and index state On main: latest modification
Unlink of file 'music.db' failed. Should I try again? (y/n) y
fatal: Could not reset index file to revision 'HEAD'.
切换分支后
PS C:UsersAdministratorDesktopprojectssongs> git checkout -b test-branch
Switched to a new branch 'test-branch'
PS C:UsersAdministratorDesktopprojectssongs> git st
M music.db
?? static/scripts/downloader.js
?? test/
PS C:UsersAdministratorDesktopprojectssongs> git add .
然后我又回来main
PS C:UsersAdministratorDesktopprojectssongs> git checkout main
Switched to branch 'main'
M .gitignore
M music.db
A static/scripts/downloader.js
A test/a.exe
A test/ipvalidator.exe
A test/nextvalidator.c
A test/nextvalidator.exe
Your branch is up to date with 'origin/main'.
PS C:UsersAdministratorDesktopprojectssongs> git branch -D "test-branch"
Deleted branch test-branch (was 08f1d8e).
然后我应用了stash
,我的更改消失了
PS C:UsersAdministratorDesktopprojectssongs> git stash apply
error: Your local changes to the following files would be overwritten by merge:
.gitignore
Please commit your changes or stash them before you merge.
Aborting
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: music.db
new file: static/scripts/downloader.js
new file: test/a.exe
new file: test/ipvalidator.exe
new file: test/nextvalidator.c
new file: test/nextvalidator.exe
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .gitignore
Untracked files:
__pycache__/
flask_session/
improvements.txt
run.ps1
static/downloads/
templates/test.html
test.py
venv/
编辑:
按照@ElderFuthark的要求,我运行了git stash show --stat
并得到了以下内容:
PS C:UsersAdministratorDesktopprojectssongs> git stash show --stat
.gitignore | 4 ++--
app.py | 11 +++++++----
music.db | Bin 69632 -> 86016 bytes
static/css/musicPlayer.css | 1 +
static/css/styles.css | 4 +++-
templates/favorites.html | 2 ++
templates/layout.html | 8 ++++++--
templates/play.html | 27 +++++++++++++++++++--------
8 files changed, 40 insertions(+), 17 deletions(-)
Long:第 2 部分(共 2 部分):您现在可以做什么
(另见第1部分)
这个的简短版本是:
- 你通过 Git 本身提供的用于恢复文件的内容,是提交中的任何内容,以及您现在可以从 Git 索引提交的任何内容;
- 您电脑上可用的内容通常包括任何未跟踪的文件以及您使用备份软件(例如 macOS 上的时间机器)进行的任何备份。
这些是取回文件的地方。
您的git stash show --stat
显示了与我一直标记K
的提交到w
中的提交的差异:
git stash show --stat .gitignore | 4 ++-- app.py | 11 +++++++---- music.db | Bin 69632 -> 86016 bytes static/css/musicPlayer.css | 1 + static/css/styles.css | 4 +++- templates/favorites.html | 2 ++ templates/layout.html | 8 ++++++-- templates/play.html | 27 +++++++++++++++++++--------
现在,您的索引中还有一个版本的music.db
(可能与此处提交w
中的版本匹配)以及A
dded 文件。 如果您愿意,可以再次git commit
或git stash
,以进行更多提交。 您仍然拥有现有的藏匿处。
您可以使用git stash branch
将任何藏匿处变成分支。 如果现有存储中有有价值的数据,这是我的一般建议:一旦它是一个正常的日常分支,您就可以使用它的所有 Git 工具。
如果 Git 的索引和你的工作树现在没有任何有价值的东西,你可以使用git reset --hard HEAD
来重置这两个
。由于music.db
存在问题,您应该找出原因:可能有一个程序已经打开或仍然打开,阻止您替换文件。 如果您可以终止该程序,或说服它关闭文件,则可以再次使用它。
因此,做这些事情中的每一个 - 例如,从您现在拥有的内容中提交,如果必要和适当,修复阻止您使用music.db
的任何内容,和/或将现有的藏匿处变成带有git stash branch
的分支。 如果藏匿处中没有任何有价值的东西,请放下藏匿处。
使用git stash branch
在使用git stash branch
之前,您需要处于"干净"状态,即git status
不谈论要提交和未暂存的更改。 (或者,您的git st
短状态别名不显示任何 M、A、D 等文件;??
未跟踪的文件有时是可以的。
然后,您可以运行:
git stash branch new-branch-for-stash
这样做的是:
- 查看
i
的父级并w
提交(这是我的图纸中的提交K
); - 使用
git stash apply --index
还原索引和工作树状态。
这几乎总是有效的(例外情况包括"某些程序保持music.db
打开状态,因此我们无法修补它";请注意,这里的提交K
和w
music.db
是不同的)。 它让你处于一种状态,你可以运行git commit
,或git add
和git commit
,或git commit
后跟git add
和git commit
。 选择您想要的更改,以提交您存储的更改,然后返回到"干净"git status
(也许,对于未跟踪的文件除外)。
未跟踪的文件是这里的常见问题,尤其是当.gitignore
随时间变化时,因为某些文件实际上可能在某些提交中,而实际上它们不应该在这些提交中,如果您成功签出这样的提交,该文件现在将位于 Git 的索引中。 Git 现在希望删除该文件,如果您从该提交切换到另一个缺少该文件的提交。
解决方案通常是显而易见的微不足道的:重命名未跟踪的文件,或者完全重命名工作树(这也不在路上)。 这样,您仍然拥有文件,并且它们仍然不在索引中。 通过为它们使用新名称,它们不会被意外(或故意但错误)存储的任何提交所破坏。
对于不在 Git中的任何文件,您需要使用一些外部 Git 机制还原它们。
很长,所以,第 1 部分,共 2 部分
(请参阅第 2 部分了解您可以执行的操作)
这里有很多东西需要学习,还有多个不同的步骤可以恢复所有内容。 两者之间有重叠,但它们并不完全相同。
作为学习项目,我建议的一件事是"不要使用git stash
",因为它经常造成一团糟。 不过,人们确实非常喜欢它。
在我们开始修复问题之前,您需要了解以下事项(您可能已经知道其中的一些,但我将在这里列举它们):
-
Git 主要是关于提交。 它与文件无关(尽管我们将文件存储在提交中),也不是真正与分支(一个定义不佳的词)有关。 我们使用分支名称来帮助我们(和 Git)找到提交,然后存储文件。
-
提交本身具有或可以具有父/子关系。 这些形成了 Git 称为分支的东西,这与 Git 也称为分支的分支名称不同。
-
提交是有编号的,带有大而丑陋的随机哈希ID 或对象 ID。 这些 ID 实际上是以十六进制表示的数字。 每次提交都会获得一个唯一的ID,一个以前从未在任何地方用于任何提交,并且永远不会再次使用。 因此,ID 用于查找提交。 Git 实际上需要那个 ID,但它们是如此之大、丑陋和随机,以至于人类非常不擅长它们,我们大多不使用它们:我们主要使用分支名称。
-
提交中的内容是一对东西:
每个提交都包含每个文件的完整快照(它保存:随着时间的推移添加或删除某些文件)。
每个提交还包含一些元数据,或有关提交本身的信息:例如,谁进行了提交以及何时提交。 对于任何给定的提交,此元数据都包括上一个提交的原始哈希 ID 的列表(通常只有一个元素长)。 Git 称之为提交的父级,这是提交如何链接成"分支"(提交组类型的分支,而不是名称 kind)。
所有这些部分都是完全只读的,只要提交本身持续存在,它们就会持续存在。
-
由于提交是只读的,因此您实际上永远无法直接处理提交。 相反,"签出"提交(使用
git checkout
或git switch
)的操作将文件从提交复制到可用的工作区。 Git 称之为你的工作树或工作树,它很简单,就是你工作的地方。请注意,签出一些提交会使该提交成为当前提交。 将来,此当前提交将成为下一个新提交的父提交,除非您切换到另一个提交作为当前提交。
-
出于只有 Git 的发明者知道的原因——尽管我们可以推测我们想要的一切——Git 不仅仅是每个"活动"文件的两个副本。确实有一个永久保存在当前提交中,还有一个可用的、可编辑的副本在你的工作树中。 大多数版本控制系统都这样做,因此有两个副本。 但是 Git 会保留提交后每个文件的第三个副本(或"副本",因为它是 Git 的内部重复数据删除形式)。 第三个副本是 Git 进行下一次提交的方式,它存储在一个非常重要但名称不佳的地方。
所以,回顾一下:
-
我们为
git checkout
或git switch
指定分支名称。 -
该名称通过其哈希 ID 定位提交。 如果此签出或切换成功,Git 将从该提交中提取文件,以便我们可以处理它们。 该提交(哈希 ID)将成为当前提交,并且该名称(无论它是什么)都将成为当前分支名称。
-
要签出该提交,Git 必须从永久保存在该提交中的文件中填写工作树和这个名称不佳的第三个副本(与提交本身一样永久,"大部分是永久性的")。
Git 的索引和你的工作树
现在是时候讨论保存每个文件的第三个副本的那个名称不佳的区域,以及它与工作树的关系。 Git 用三个不同的名字来称呼这个东西:
-
它是索引。 这个名字没有明显的含义,既有好也有坏:好,因为你可能不会带来一堆先入为主的观念,坏的,因为,嗯,对"索引"的反应主要是"嗯?什么?
-
它也是集结区。 这通常是一个更好的名称,因为它谈到了我们如何使用它。 我们
git add
一个文件来"暂存"它以进行提交。 但这有点误导,因为该文件甚至在我们git add
之前就已经在暂存区域中。或者更确切地说,如果它已经在当前提交中,它就在那里。 -
作为一个更古老的名称,现在大部分都已废弃,Git 有时将其称为缓存。 您主要在标志中看到这一点,例如在
git rm --cached
或git diff --cached
中。
无论如何,你需要知道的关于这个东西(我在这里称之为索引)是它保存你提议的下一次提交。 也就是说,如果你现在运行git commit
,无论何时"现在",你都会得到一个新的提交或错误。 如果您没有收到错误,您将获得保留的新提交,因为它是每个文件的完整快照、索引中的所有文件,根本没有其他文件。 将在新提交中的文件的特定版本是我们在文件的索引副本中找到的数据。
由于此索引副本是第三个副本(但预先删除了重复数据,以便git commit
快速执行),因此它可能与当前提交副本不同。 它也有可能与工作树副本不同。
当我们第一次签出一些提交时,Git 通常会——稍后会看到一个异常——立即将提交中的所有文件复制到其索引/暂存区域。 因此,提交和索引将匹配。而且,Git 通常会将所有文件从提交/索引(匹配)复制到工作树中。 因此,提交、索引和工作树都匹配。
在这种情况下,当一切都匹配时,git status
通常很安静:
$ git status
On branch main
nothing to commit, working tree clean
例如。
如果更改工作树中的文件并运行git status
,则会得到:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: Makefile
例如。 如果随后git add
该文件,这将再次更改:
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: Makefile
这里发生的事情很简单:git add
告诉 Git:阅读文件的工作树副本。 将其压缩为 Git 的内部重复数据删除存储格式,使其准备好提交。 检查这是否是某些现有文件的副本;如果是这样,请丢弃您所做的压缩工作并重用副本,如果没有,您现在已准备好准备好的压缩文件进行提交,无论哪种情况,请更新索引,以便下一次提交将使用文件的此更新版本。
换句话说,git add
将文件复制到索引中。 索引已准备好进入下一次提交。 它仍然准备好进入下一次提交。 但是现在它有添加文件的不同版本。
如果您git add
的 fie 是全新的(如果它之前不在索引中),Git 仍会删除重复的内容。 这意味着,如果你在一个提交中存储了十个文件,但它们都是相同的,Git 只存储它的一个副本,不到十个名称。 然后 Git 更新索引/暂存区/缓存中的名称和内容数据,以便下一次提交将包含此文件。 如果文件之前在索引中,Git 会踢出旧副本,以便为新副本腾出空间。无论哪种情况,索引都会继续准备好提交。 索引始终保存建议的下一个快照。
那么,你提交的方式是:
- 提取一些现有的提交到索引和工作树;
- 处理文件(在您的工作树中);
- 使用
git add
更新索引中建议的快照;以及 - 运行
git commit
.
当你运行git commit
时,Git:
- 从索引/暂存区域打包文件:这是新快照;
- 添加必要的元数据,包括当前提交哈希 ID 作为新提交的父级;
- 将所有这些写入 Git 数据库(该数据库保存提交和其他对象,按其哈希 ID 编制索引),以便获取新的提交哈希 ID。
然后,Git将新提交的哈希 ID 填充到分支名称中。
进行新提交的图片
这意味着我们可以像这样绘制一个 Git "分支":
... <-F <-G <-H <-- your-branch (HEAD)
在这里,分支的名称是your-branch
。 您"位于"此分支上,因为您使用了名称为your-branch
的git switch
或git checkout
。 这个名字包含提交H
的哈希 ID —H
代表一些像这里30cc8d0f147546d4dd77bf497f4dec51e7265bd8
这样的大丑陋哈希 ID。 所以H
是当前的提交,也是分支上的最后一次提交。
您正在处理/处理的文件来自提交H
。 它们现在位于您的工作树中,以及 Git 的索引/暂存区域中。
提交H
有一个父提交,我们称之为G
,它也有一个快照和元数据,并且该提交有一个我们称之为F
的父提交,它还有一个快照和元数据,等等。 但是我们正在使用提交H
。
现在,您修改一些文件并git add
它们以更新 Git 的索引,并运行git commit
。 Git 将每个文件(包括提交H
中所有去重复的、未更改的重复项(由于它们已删除重复数据)保存到新快照中,并进行新的提交,该提交将获得新的唯一哈希 ID,但我们将其称为I
。 提交I
将提交H
的哈希ID作为其父级,因此I
指向后H
:
...--F--G--H <-- your-branch (HEAD)
I
现在,作为git commit
的最后一步,Git 将I
的哈希 ID 塞进了名称your-branch
中,以便这个名称指向I
而不是H
:
...--F--G--H
I <-- your-branch (HEAD)
(现在没有理由在行中弯曲来绘制提交,但我将其保留下来以明确移动的是分支名称)。
创建新的分支名称
假设在我们进行提交I
之前,我们想把它放在不同的分支上。 这就是括号中的名字HEAD
的用武之地。 如果我们想创建一个新的分支名称,我们可以使用git branch
来做到这一点:
...--F--G--H <-- your-branch
我们运行:
git branch new-branch
并获得:
...--F--G--H <-- new-branch, your-branch
我们现在需要一种方法来说明您正在使用哪个分支名称,而该"方法"是将特殊名称HEAD
附加到一个分支名称:
...--F--G--H <-- new-branch, your-branch (HEAD)
这意味着您"在"的分支是your-branch
,而不是new-branch
。 但是,这两个名称都选择提交H
,您现在可以:
git checkout new-branch # or git switch new-branch
获得:
...--F--G--H <-- new-branch (HEAD), your-branch
记得我在上面说过,
当我们第一次签出一些提交时,Git 通常会——稍后会看到一个异常——立即将提交中的所有文件复制到其索引/暂存区域。
我们刚刚遇到了第一个例外。 Git 故意有点鬼鬼祟祟。 如果我们从一个分支名称切换到另一个分支名称,Git 会利用提交保留删除重复的文件这一事实来避免工作。 对于在两次提交中重复的每个文件,Git不执行任何操作。 当你从提交H
移动到提交H
时,根据定义,所有文件都是重复的,所以 Git 根本不需要做任何事情——而且它不需要!
因此,无论您是否修改了索引和/或工作树中的任何文件,因为将 commitH
的文件复制到索引和工作树中的git checkout
,您从H
切换到H
除了将HEAD
附加到另一个分支名称之外,绝对没有任何作用。
这意味着您可以随时创建一个新的分支名称并切换到它,唯一实际更改的是新名称现在是您当前的分支名称。您当前的提交和所有三个副本中的所有文件都不受干扰。
你可以像你所做的那样将 create-new-name 和 switch-to-name 与git checkout -b
或git switch -c
结合起来;这没有真正的区别,除了 Git 不会创建分支名称,除非开关可以工作。
跟踪的文件,或者,我们需要了解的有关索引的最后一件事
我将其保存在一个单独的部分,因为它在这里变得有点复杂,并且在某些情况下非常关键。
正如我们已经看到的,Git 的索引始终保存您提议的下一次提交 - 或者更确切地说,它的快照;Git 不会组装元数据,直到您实际运行git commit
. 因此,索引保存将在下一次提交中的文件的副本(或删除重复的"副本",实际上是)。
有时,您还会在git status
的输出中看到Untracked files
。 但是,确切地说,什么是未跟踪的文件? 幸运的是,答案非常简单:在 Git 中,未跟踪的文件只是当前不在 Git 索引中的任何文件。请注意,文件是否在任何提交中并不重要。 它现在必须不在 Git 的索引中。 (当然,它也必须存在于你的工作树中。 否则,我们可以说文件rumplestiltskin.straw
几乎在每个存储库中都是未跟踪的,因为它不在索引中,但也不在工作树中。 我们通常不会评论不存在的东西。
相比之下,跟踪的文件是 Git 索引中的文件。它不必在任何提交中,甚至不必在您的工作树中。 因此,跟踪的文件只是建议的下一次提交(或其快照)中的那些文件。 未跟踪的文件是不在建议的下一次提交中的文件。 您可以使用git add
跟踪未跟踪的文件,也可以使用git rm
(带或不带--cached
)从 Git 的索引中删除跟踪的文件。 使用不带--cached
git rm
会将其从 Git 的索引和您的工作树中删除,因此我们不再考虑它,但git rm --cached
将其从 Git 的索引中删除而不将其从您的工作树中删除,使其无法跟踪。
当然,请记住,Git 的索引有时会被填充,因此跟踪的文件集会发生变化。 当然,您运行git add
和/或git rm
,这也可以更改跟踪的文件集。 所以"跟踪"并不是一个永久的东西:它总是基于 Git 索引中的内容。
当我们运行git status
,它经常抱怨各种文件被取消跟踪。此投诉的目的是提醒我们git add
文件。但有时我们不想git add
这些文件。这就是.gitignore
的意义所在:它只影响未跟踪的文件,并停止抱怨。
不过,它也会影响我们跑步的任何集体git add
。 为了使事情使用起来更方便,Git 让我们运行git add .
或git add --all
,Git 此时扫描工作树以查找要git add
的文件。 Git 将:
- 添加更改的文件,以便更新索引副本,以及
- 将新文件添加到 Git文件,以便跟踪新的未跟踪文件。
- 作为一个奇怪的特例,
git add
甚至可以删除一些文件,尽管我不会在这里介绍这种情况。
但是,如果 Git 的新文件应该处于未跟踪状态并保持未跟踪状态,则添加新的 Git 未跟踪文件将是错误的。 因此,在.gitignore
中列出未跟踪的文件会告诉 Git 不仅要闭嘴,而且还不要使用这些"添加所有文件"操作之一来添加它。
在.gitignore
中列出跟踪的文件不起作用,因为此类文件已在 Git 的索引中。 这不是您这里问题中的关键项目,但了解一般情况很重要。.gitignore
这个名字有点误导:它也许应该是.git-do-not-complain-about-these-files-if-they-are-untracked-and-when-they-are-untracked-do-not-add-them-with-en-masse-git-add-commands-either-because-they-are-supposed-to-stay-untracked
,或者类似的东西。 但是谁愿意将其键入为文件名? 原来如此.gitignore
。
简单使用git stash
(注意:从 Git 版本 2.13 开始,git stash save
已被弃用,取而代之的是git stash push
。 当用于我将在这里描述的简单情况时,两者都做同样的事情,但git stash push
可以选择做更花哨的事情。
git stash
做什么可以简单地描述——在某些情况下可能太简单了,但我们在这里不会担心那些——比如:进行根本不在分支上的提交,然后使用git reset --hard
。 这就是你在这里所做的,与:
PS C:UsersAdministratorDesktopprojectssongs> git stash save "latest modification" Saved working directory and index state On main: latest modification
在这一点之后,有些事情有点不对劲,但现在我要忽略它。
这里的git stash save
操作进行了两次提交 — 存储本身由两个或三个提交组成,但我们不需要担心这里的三次提交种类 — 并且保存了这两个提交:
- 索引的状态(即,
git commit
将创建的相同快照),以及 - 工作树的跟踪文件的状态:即,如果您运行
git add -u
(更新跟踪文件)并git commit
,您将在新提交中获得的文件。
这些时髦的git stash
提交的元数据有点奇怪,但如果将git stash save
与消息一起使用,则会为git stash list
将显示的提交设置提交主题。
这两个新提交根本不放在任何分支上,这意味着git log
通常不会显示它们。 (它们在refs/stash
所以git log --all
确实显示它们。 它们看起来很奇怪,因为其中一个提交在技术上是合并提交,即使它不是使用git merge
的结果,也不应被视为merge commit
。 这是您必须使用git stash
来处理这些特殊提交的原因之一,也是我建议避免git stash
的原因,因为只有git stash
才能正确处理它们。 这使得许多通常的 Git 工具箱变得不那么有用。
现在让我们看看你到底做了什么
您的计算机输出从这里开始:
PS C:UsersAdministratorDesktopprojectssongs> git checkout feature error: Your local changes to the following files would be overwritten by checkout: .gitignore app.py music.db static/css/styles.css templates/favorites.html templates/layout.html Please commit your changes or stash them before you switch branches.
这意味着你的git checkout
实际上没有做任何事情。 它只是报告了工作树中的哪些文件具有未提交的工作(可能是git add
-ed,可能不是git add
-ed,但不是git commit
-ed),如果 Git 要切换到分支名称feature
的最后一次提交,Git 将不得不从 Git 的索引和您的工作树中删除。
Git 没有切换,所以文件仍然完好无损(在 Git 的索引和你的工作树中)。
PS C:UsersAdministratorDesktopprojectssongs> git stash save "latest modification" Saved working directory and index state On main: latest modification
在这里,git stash
进行了两次提交,保存了当前索引状态 以及跟踪文件的状态。 然后 Git 运行git reset --hard HEAD
尝试:
- 使索引与当前提交匹配,并且
- 使工作树与索引匹配(未跟踪的文件除外:这些文件保持不变)。
但是出了点问题:
Unlink of file 'music.db' failed. Should I try again? (y/n) y
致命:无法将索引文件重置为修订版"HEAD"。
可能(因为您在Windows上)某些程序打开了文件music.db
,从而阻止了其删除。 目前尚不清楚在重置索引文件方面还可能出现什么问题,但仅此一项就足以使git reset --hard HEAD
失败。
PS C:UsersAdministratorDesktopprojectssongs> git checkout -b test-branch Switched to a new branch 'test-branch'
这将创建一个新的分支名称,指向您已经进行的同一提交。 我在这里画的画会错,但我不能画对的;只有你能做到。
I--J <-- feature
/
...--G--H <-- main
K <-- somebranch, test-branch (HEAD)
PS C:UsersAdministratorDesktopprojectssongs> git st M music.db ?? static/scripts/downloader.js ?? test/
大概git st
是git status --short
的别名,所以这个git status
告诉我们:
- 跟踪
music.db
,并且提交版本和索引版本匹配,但索引和工作树版本不同; static/scripts/downloader.js
未被追踪;和- 有多个未跟踪的文件按
test/
汇总(因此,在该文件夹中的某个位置)。
PS C:UsersAdministratorDesktopprojectssongs> git add .
这会批量添加所有修改的文件,即music.db
,以及所有被抱怨的未跟踪文件,即static/scripts/downloader.js
和文件(我们不知道它们的名称)在test/
。
(旁注:虽然 Windows 使用反斜杠(例如test
)将顶级文件夹名称与该文件夹中的其他名称分开,但 Git 本身并不完全相信文件夹,并且始终使用正斜杠。 Git 的索引只能存储文件,不能存储文件夹,它将它们存储在static/scripts/downloader.js
等名称下,并带有正斜杠。 Git 知道如何将其分解为文件夹和文件片段并与操作系统交互,这可能需要向斜杠而不是正斜杠,但在内部 Git 总是使用正斜杠。 由于索引本身只保存文件名,并带有正斜杠,Git 无法在提交中保存空目录:操作系统级文件夹创建始终只是在这里暗示的。 如果索引可以在此处保存目录条目,Git 将能够存储空文件夹。
当然,该添加会更新 Git 的索引。 因此,索引现在与工作树匹配,除了真正忽略的文件(在工作树中未跟踪且也列在.gitignore
中的文件)。
尚未发生任何提交,因此仅此而已。 接下来我们有:
PS C:UsersAdministratorDesktopprojectssongs> git checkout main Switched to branch 'main' M .gitignore M music.db A static/scripts/downloader.js A test/a.exe A test/ipvalidator.exe A test/nextvalidator.c A test/nextvalidator.exe Your branch is up to date with 'origin/main'.
在这里,Git 利用了我再次提到的特殊情况。 但是,我们确实必须切换提交。 与我描述的H
到H
的情况不同,Git 确实必须从一个提交切换到另一个提交。 我在上面绘制的(错误的)图表说我们从提交K
切换到提交H
:
I--J <-- feature
/
...--G--H <-- main (HEAD)
K <-- somebranch, test-branch
现在让我补充一下这张图,以显示git stash
所做的两个提交,因为它们很快就会很重要。 他们在这里:
I--J <-- feature
/
...--G--H <-- main (HEAD)
K <-- somebranch, test-branch
|
i-w <-- refs/stash
请记住,您尝试git checkout feature
失败了,使您处于分支somebranch
。 那是你跑git stash
时所处的位置. Git "挂起"当时当前的提交的两个提交,我将其绘制为提交K
。 提交i
保存索引状态,提交w
保存git add -u
工作树状态。 然后,git reset --hard
尝试将所有跟踪的文件重置回其somebranch
状态,但至少失败了music.db
。
但是,现在我们将提交H
作为我们当前的提交。git switch
命令能够在切换提交时不擦除music.db
,因为提交K
中music.db
的重复数据删除副本与提交H
中music.db
的重复数据删除副本匹配。 因此,即使该文件在工作树中被"修改"(由于git reset --hard
失败),Git 也能够在工作树中保留修改。
成功完成git checkout
文件更新后,git checkout
现在运行一种git status --short
,减去一些信息。 此git status --short
的目标是向您展示索引和/或工作树中现在的不同之处,与提交中本来会写入索引但不必的内容不同之处:
M .gitignore M music.db
这两个文件不同。 也就是说,git checkout
会用 commitH
的副本替换.gitignore
和music.db
,但它不必这样做,所以它跳过了它。 (它们有什么不同? 是索引、工作树还是两者兼而有之? 不像git status --short
,这没有说。
A static/scripts/downloader.js A test/a.exe A test/ipvalidator.exe A test/nextvalidator.c A test/nextvalidator.exe
这五个文件的不同之处在于它们现在在您的索引和工作树中,但不在提交H
中。
PS C:UsersAdministratorDesktopprojectssongs> git branch -D "test-branch" Deleted branch test-branch (was 08f1d8e).
这去掉了test-branch
这个名字,它曾经用来查找提交K
。 幸运:
- 您的屏幕上有缩写的哈希 ID:它是
08f1d8e
. 您可以使用它来查找提交。 - 您还知道,无论您之前在哪个分支上(我在上面的图纸中
somebranch
调用的分支),这个相同的名称也指向提交08f1d8e
,因此您可以使用该名称。 (可能不是somebranch
。
接下来,您运行了:
PS C:UsersAdministratorDesktopprojectssongs> git stash apply error: Your local changes to the following files would be overwritten by merge: .gitignore Please commit your changes or stash them before you merge. Aborting
无论运气好坏,git stash apply
决定无法应用藏匿处。 因此,它什么也没做。 请注意最后一行,Aborting
。
然后你有一个完整的git status
输出(如果没有git status
命令,这似乎有点奇怪,但也许git stash
运行它 - 我通常避免git stash
并且它已经发展了几次,我不确定哪些版本在这一点上做了什么)。 这给了我们很多信息:
Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: music.db new file: static/scripts/downloader.js new file: test/a.exe new file: test/ipvalidator.exe new file: test/nextvalidator.c new file: test/nextvalidator.exe
这是HEAD
(在我的绘图中提交H
)和 Git 索引之间不同的文件列表,就好像我们或多或少地运行git diff --staged --name-status
一样。 我们看到索引中的music.db
与提交H
中的music.db
不同,并且之前添加的五个文件仍然存在于 Git 的索引中。 因此,这些更改是git checkout main
能够在 Git 的索引中保留的。
Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: .gitignore
这是 Git 的索引和你的工作树之间不同的文件列表,就好像我们运行git diff --name-status
一样。 这里只列出了一个文件,.gitignore
。 因此,这必须是您为允许跟踪static/
和test/
文件所做的编辑,git checkout
在当前提交H
提交时能够不受干扰。 不过,在 Git 的索引中没有什么不同:git add
以某种方式跳过了这个。 它可能更早地被git reset --hard
恢复了(不像music.db
出了问题)。 因此,这意味着提交K
中的.gitignore
与此处的.gitignore
相匹配 - 尽管其中一些是猜测。 (这完全取决于git reset --hard
失败期间发生了什么。
Untracked files: __pycache__/ flask_session/ improvements.txt run.ps1 static/downloads/ templates/test.html test.py venv/
这将列出工作树中不在 Git 索引中的文件。我认为它们会更早地被git add .
添加,所以当你在提交K
时它们一定不存在。 那好像...奇怪,并对我上面的分析产生了一些怀疑。 同样,鉴于git reset --hard
的失败,我只是不确定这些。