在分支切换之间保留 git --assume-unchanged 文件

42

我一直在使用git --assume-unchanged yacs/settings/development.py来忽略我的开发分支中的本地数据库配置文件。但是当我想要切换分支(用于部署)时,会出现一个错误提示说我仍然有未完成的更改:

% git checkout production
error: Your local changes to the following files would be overwritten by checkout:
    yacs/settings/development.py
Please, commit your changes or stash them before you can switch branches.
Aborting

这很烦人。我知道绕过这个问题的唯一方法就是将其隐藏:

% git stash
% git checkout production
% git merge dev
% ./deploy.sh
% git checkout dev
% git stash pop
# On branch dev
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   yacs/settings/development.py
#

但是现在它又回到了索引中(呃)!有没有更好的替代工作流程?

[我并不特别在意本地更改保留在本地(也就是说,如果是生产分支,把更改保留在本地也没关系),我只是不希望将其推送到远程存储库。]


这对我不起作用。我使用 --skip-worktree 添加了文件,但 git status 没有显示。然而,我仍然无法切换到另一个分支。我错过了什么? - sandyp
4个回答

22
你可以尝试(git update-index手册页面):
git update-index --skip-worktree -- path

跳过工作树标记可以用一句话来定义:当读取一个条目时,如果它被标记为跳过工作树,则Git假装它的工作目录版本是最新的,并读取索引版本。但是如 "git assume unchanged vs skip worktree" 中所述,这两个选项都有问题。"--assume-unchanged" 在索引被丢弃(例如 "git reset")时会重置自己,因此迟早会使您感到困惑。"--skip-worktree"也是一样。
此外,请确保使用Git 2.24(2014年第四季度)。 自2012年(原始问题)以来,git stash已被移植到C语言中(不再是shell脚本),但是它在新实现中必须(重新)学习将刷新的索引写回磁盘。 请参见提交34933d0(由Thomas Gummerer (tgummerer)于2019年9月11日提交)。 (由Thomas Gummerer -- tgummerer --合并于提交34933d0,2019年9月20日)

stash: 确保写入刷新后的缓存

在将stash转换为C语言时,调用'git update-index --refresh'的方式被'refresh_cache()'函数替换。
只要索引仅需要在内存中使用,而不是从磁盘重新读取,这样做就可以了。

然而,在许多情况下,我们确实需要将刷新后的索引写入磁盘,例如'merge_recursive_generic()'在重新从磁盘读取之前会丢弃内存中的索引,在'apply --quiet'的情况下,我们当前使用的'refresh_cache()'没有写入索引到磁盘是没有意义的。

始终在刷新后将索引写入以确保与脚本stash相比没有退化。
在未来,我们可以考虑在确定后续调用实际上不需要刷新缓存并且不需要在stash退出后或已经写入其他位置后刷新时,尽可能避免写入。


警告,当使用--quiet选项与git stash一起使用时,该索引可能不会被正确重写:自Git 2.25(2020年第一季度)以来,对"git stash pop"的最近更新使得该命令在运行时清空索引,这已得到纠正。

请看提交df53c80(2019年11月13日),作者是Thomas Gummerer (tgummerer)
(合并于提交3c3e5d0,由Junio C Hamano -- gitster --于2019年12月1日处理)

stash:确保在写入索引之前我们拥有有效的索引

报告人:Grzegorz Rajchman
签署者:Thomas Gummerer

在'do_apply_stash()'中,我们最终会刷新索引。

34933d0eff ("stash: make sure to write refreshed cache",2019-09-11,Git v2.24.0-rc0 -- merge listed in batch #6)以来,当使用参数'--quiet'调用'git stash apply'时,我们还会写入刷新后的索引。

但是,如果没有使用参数'--index'调用'git stash apply',则在'else'子句中也会丢弃索引。

我们需要这样做是因为我们使用一个外部的'git update-index --add --stdin',这将导致内核索引过时。

稍后我们调用'refresh_and_write_cache',现在会导致写入被丢弃的索引,这意味着我们实际上是在写入一个空的索引文件。

显然,这是不正确的,或者说不符合用户的预期行为。

我们不应该在未经请求的情况下修改用户的索引。

确保在丢弃当前内核索引后重新读取索引,以避免处理过时的信息。

相反,我们也可以完全放弃'discard_cache()'和'read_cache()',但这会使我们容易陷入与34933d0eff相同的陷阱,因此最好避免这种情况。

在静默情况下,我们还可以完全放弃'refresh_and_write_cache'。

在旧版stash中,我们依赖于'git status'在传递'--index'至'git apply'时,在调用'git read-tree'之后刷新索引。

然而,替换'git read-tree'的'reset_tree()'调用总是传递与'-m'等效的选项,使得刷新索引变得不必要。


8
尝试过--assume-unchanged--skip-worktree两种方法,但在设置此属性后,当我尝试切换本地分支时,两者都会继续抱怨有未提交的更改。 - amoe
4
@amoe,那个文件已经有版本记录了对吧?如果你把那个文件加入到 your_repo\.git\info\exclude 文件中会怎么样? - VonC
1
@VonC,script.js文件已经使用了--assume-unchanged--skip-worktree,但仍然出现相同的错误。你有任何想法吗? - Eray
@VonC 谢谢。这基本上就是Eray在这里问的问题:https://dev59.com/Vl0b5IYBdhLWcg3wJ-Uz - sandyp
@sandyp 那么最好完全取消跟踪该文件(在两个分支中使用git rm --cached -- afile,如https://dev59.com/Vl0b5IYBdhLWcg3wJ-Uz#29549545),如果可能的话,通过内容过滤驱动程序生成其内容,如https://stackoverflow.com/a/45025658/6309。 - VonC
显示剩余4条评论

3
我使用的解决方案是使用--skip-worktree。然而,像一些人一样,在我设置了想要保留本地更改的文件的--skip-worktree标志后,即使我在分支和主分支之间切换,Git仍然会抱怨。
如果你在运行--skip-worktree之前对本地文件进行了更改,就会遇到这个问题。
有一个建议的解决方法是将该文件添加到your_repo/.git/info/exclude中。但我不想将文件添加到排除列表中,所以我在工作树目录中执行了以下操作:
1. cp <local-only_file> ~/
  • 将具有本地唯一更改的文件复制到文件系统上的某个安全位置
2. git checkout <local-only_file>
  • 在工作树中,检出文件以匹配主分支文件
3. git update-index --skip-worktree -- <local-only_file> 4. cp ~/<local-only_file> .
  • 从安全位置把相关文件复制回来
5. git diff
  • 不应显示任何更改;如果你推送到主存储库,则不包括<local-only_file>中的更改

3
我开始做的是,从主分支(master)创建一个名为private的分支来保存我的本地修改。可以将其视为我工作分支和主分支之间的代理分支。当我需要使用本地修改时,可以将当前工作分支(rebase)到private上,并在.gitconfig中设置几个别名(alias)以自动更新private与master的同步。当我需要合并到主分支时,我的别名会确保首先将我的工作分支(rebase)到private,然后再rebase到主分支(master)。
我在这里发布了有关此更多详细信息的博客文章:http://blog.ericwoodruff.me/2013/02/git-private-branch-pattern.html

你评论中的链接无法打开,但我喜欢你回答中描述的想法。 - Flimm
感谢您的反馈,我已经在原始答案中放置了更正后的博客链接。 - Eric Woodruff

1
这个问题发生在你跳过的文件与你试图检出或拉取的分支中的文件不相同时。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接