我的项目中有一个文件,我希望在本地进行更改,而每次我从存储库中提取文件时都不会覆盖该文件,即我想拒绝对该特定文件的传入更改。到目前为止,我的解决方案是进行git stash --> git pull --> git stash pop
该文件位于.gitignore
本地和存储库中。我试过了
git update-index --assume-unchanged
和git update-index --skip-worktree
,但无济于事。我本来打算做git rm --chached
,但从我读过的内容来看,这似乎会从存储库中删除文件,这不是我想要的。
答案 0 :(得分:3)
该文件位于
.gitignore
本地和存储库中……
如您所见,如果文件确实被提交,那么这样做是没有用的。这是因为.gitignore
并不意味着忽略此文件,它的确意味着针对此文件如果未被跟踪闭嘴。如果未跟踪 (如果已跟踪),则列出文件完全没有效果。
我尝试过
git update-index --assume-unchanged
和git update-index --skip-worktree
,但无济于事。
为便于确认,如果您确切地指出问题出在哪里,将会很有帮助。现在,我假设问题出在哪里,那些命令似乎起作用了—它们根本没有抱怨—但是后来的git fetch && git merge
抱怨说它将覆盖文件的内容。 (您可能会拼写此git pull
,但如果这样,我建议将其拆分为两个组成部分的Git命令,直到您真正了解每个命令各自执行的操作为止。)
这是使事情变得复杂的地方。我们必须了解Git的提交和合并模型。随之而来的是 index (又是临时区域又是 cache )的角色,以及工作树的角色。在git merge
期间。
首先,让我们快速回顾一下提交和合并过程。您已经知道 1 ,每个提交都具有所有提交文件的完整快照,并且每个提交都包含其父提交的哈希ID。因此,如果我们要在git fetch
之后但在git merge
运行之前绘制提交图,我们可能会看到:
G--H--I <-- master (HEAD)
/
...--F
\
J--K--L <-- origin/master
1 如果您尚不知道这一切,请阅读有关Git的更多信息,例如chapter on branching in the Git Book。
在这种情况下,git merge
要做的是找到共享提交,这是您的提交I
和您的名字{ {1}}指向您的master
指向的提交及其提交L
。在这里,提交origin/master
。
接下来,Git将把保存在提交F
中的内容与保存在您自己的最新提交F
中的内容进行比较。这告诉了Git 您所做的更改。比较结果与您运行后可以查看的结果相同:
I
Git还将比较保存在提交git diff --find-renames <hash of F> <hash of I> # what we changed
中的内容与其保存在最新提交F
中的内容。这告诉Git 他们发生了什么变化:
L
现在,您可以了解git diff --find-renames <hash of F> <hash of L> # what they changed
的实际工作原理:它将您所做的更改与他们所做的更改结合在一起。使用合并的更改,Git提取使用基本提交(commit git merge
)保存的内容,并将 combined 更改应用于两组更改中的所有文件。如果一切顺利,结果将是应作为合并提交的快照。然后Git将执行此操作,提交合并并调整您当前的分支:
F
被冻结为提交的文件存在一个基本问题:文件被(a)冻结,并且(b)以特殊的,压缩的,仅Git的形式存在。 frozen 部分非常适合源代码管理:这些文件将永远不会更改,因此您可以通过签出旧提交来始终取回以前的工作。特殊的仅压缩Git压缩形式对于保持您的存储空间很有用:由于Git曾经保存过每个文件的每个版本,因此如果不需要对其进行特殊压缩,您可能会用完磁盘空间相当快。但这会带来一个问题:如何获取冻结的文件?您如何更改它?
Git的答案是工作树。对某些提交执行 G--H--I
/ \
...--F M <-- master (HEAD)
\ /
J--K--L <-- origin/master
,以扩展并解冻该提交中保存的文件。解冻后的重组文件进入您的工作树,您可以在其中使用它们并进行更改。
在其他版本控制系统中,这就是故事的结尾:您拥有无法更改的冻结文件以及可以进行冻结的工作树。但是Git会添加这种中间形式,Git称为索引,临时区域或缓存,具体取决于Git的谁/哪个部分正在执行此调用。
了解索引对于使用Git至关重要,但是很少对其进行很好的解释。人们(和IDE)尝试将其覆盖并向您隐藏。这不起作用,并且不起作用的原因很重要,尤其是在您的情况下。
我对索引最了解的描述是它是下一次提交的内容。当Git提取冻结的文件时,它首先 just 解冻它们,而不是直接将它们解冻并解压缩到您的工作树中(或更准确地说,将它们收集到一个统一的列表中, t冻结-与提交中结构化,冻结的列表相反。这些现在未冻结的副本将进入索引。它们仍然都是经过Git验证,压缩并占用最少存储空间的。
一旦文件解冻到索引中,Git才将其解压缩为工作树格式。因此,它是第一个未冻结的(索引副本),然后然后提取到工作树中。这意味着索引具有准备好冻结到 next 提交中的副本。
如果您在工作树中更改文件,则必须在该文件上运行git checkout
以便对其进行 copy (并压缩和Git-ify),以使其适合指数。现在,索引副本与工作树副本匹配,只是索引副本采用特殊的仅Git形式。 现在,可以进行下一次提交了。
这是git add
的工作方式:对于每个文件,它将工作树副本与索引副本进行比较,如果它们是不同的,则表示文件是没有上演。它还将索引副本(特殊的仅Git格式)与git status
提交副本进行比较,如果索引副本不同,则表示文件已暂存为 commit < / em>。因此,如果工作树中有10,000个文件,并且索引和HEAD
提交,则实际上总共有30,000个副本(10k x 3副本)。但是,如果这三个副本中只有两个 不同,则HEAD
中只会列出两个文件(而经过Git修饰的副本相对较小)。
在运行git status
之前,索引中不同的文件只是索引中的不同。当您执行运行git commit
时,Git冻结索引(甚至不查看工作树!),并将其作为您的新git commit
提交。现在,您的新提交与索引匹配,因此文件的所有索引副本现在都与它们的“ HEAD提交副本”匹配。
(此外:在发生冲突的合并过程中,索引将扮演扩展角色。它现在不仅保留每个文件的一个副本,而且还保留最多个每个文件的三个副本。但是我们在这里不考虑冲突的合并,因此我们不必为此担心。)
现在我们可以看到这两位的作用。运行HEAD
时,Git通常会将每个文件的工作树副本与同一文件的索引副本进行比较。如果不一样,Git会说您所做的更改不是暂未提交。 (如果文件根本不在索引中,Git会说该文件是 untracked ,而{em> then git status
文件很重要。但是如果文件已在索引中,已跟踪文件,而.gitignore
文件无关紧要。)
如果您设置了假设不变或跳过工作树位,则.gitignore
不会将文件的工作树版本与索引版本进行比较。它们可以根据您的喜好而不同,git status
对此一无所知。
请注意,git status
完全忽略了这些位!它只是冻结索引副本。如果索引副本与工作树副本匹配,则意味着在再次提交文件时,文件保持相同。您的新提交与先前的提交具有相同的冻结副本。您的索引副本继续与您的git commit
提交副本匹配。
问题出在Git需要更改文件时。假设您已经设置了skip-worktree位(通常,这是您应该设置的位,因为另一个实际上是另一个问题的意思,尽管实际上任何一个都起作用)。您还修改了工作树副本。运行HEAD
不会抱怨,因为git status
实际上不会再将工作树副本比较到索引副本。
但是现在您运行git status
,并且合并要对文件进行更改。例如,Git将提交git merge
与提交F
和I
进行比较,发现尽管您尚未提交L
中文件的新版本,但 确定在I
中提交文件的新版本。因此,Git会进行更改,使这些更改进入新的合并提交L
,然后...将M
提取到您的工作树中,破坏文件的副本。
弄乱文件很不好,因此Git不会这样做。相反,它只是使合并失败。
最终,您必须做的是将文件的版本保存在某个位置。通过将文件复制到存储库之外,这可以在Git内部(例如作为提交)或在Git外部。然后,您可以将更改与更改合并,也可以在获取版本后重新进行更改。
M
实际上就是这样做的。它会提交一次-实际上,有两个 提交。这些提交的特别之处在于它们不在 any 分支上。进行提交后,git stash
运行git stash
来放弃对索引和工作树中文件的更改。您没有索引更改,即使您已将索引副本保存在隐藏提交中,所以重置的git reset --hard
部分是安全的,并且您的工作树副本也保存在隐藏提交中,因此重置的--mixed
是安全的。现在您的索引和工作树是干净的,您可以安全地合并了。然后--hard
(实际上是git stash pop
)可以使用安全性较低的内部合并(仅适用于工作树)将文件的隐藏工作树版本与文件的当前版本合并复制。 git stash apply && git stash drop
步骤会删除隐藏提交,以使它们成为未引用 2 ,并最终将其完全删除。
这里有多种使用drop
的替代方法,但是没有一个是那么简单的,而且没有一个是漂亮的。您最好使用git stash
。
最后,您可以完全停止提交文件。一旦文件不再在索引中,它就会成为未跟踪的并且不在任何 future 提交中。在我看来,这最终是最好的解决方案,但它有一个非常巨大的缺点:文件已经在过去提交过。这意味着,如果您签出 old 提交(确实有文件),则文件将位于当前提交(您刚刚签出的那个旧提交)和 索引,并将对其进行跟踪。当您从旧提交切换到没有没有文件的新提交时,那个将删除该文件!这就是您说的意思:
我当时正在考虑做
git stash
,但是据我看来,这似乎会从存储库中删除文件...
具体来说,它从 index 中删除文件,而保留工作树副本。该不会从存储库中删除文件,但是存储库本身主要由 commits 组成,并且“从存储库中删除”是nonsense phrase。您实际上不能从现有的提交中删除文件:它们会被永久冻结。您只能避免将文件放入将来提交中。
这有效,但是留下了我上面概述的陷阱:返回到历史提交会将文件还原到索引(因为git rm --cached
表示从提交中填充索引,并使用该索引来填充工作-tree )。一旦在索引中输入,它将在将来的提交中。切换到没有拥有文件的提交,需要从索引中删除它,这意味着从工作树中删除它,现在工作树副本是不见了。
因此,如果您想走这条路,这是个好方法,则应该:
git checkout commit
config.sample
中),并将其全部提交一次,之后将不再使用旧的配置文件。在切换到新版本的$HOME/.fooconfig
程序之前,告诉人们将其配置移动到新位置。使它成为主要版本,因为行为不同。
2 请参见Think Like (a) Git。