分支之间的Git Smudge/Clean过滤器

25

涉及笔刷/清洁过滤器的相关问题有很多 - 我花了几个小时阅读它们,并尝试了各种选项,但仍然失败。我希望我能以一种方式提出问题,以便我可以得到适用于我的答案。

具体来说,我已经阅读了大多数这些答案链接回到的页面:


tl;dr

这是一个详细的问题,但摘要如下:

  • 我可以使用笔刷/清洁过滤器将 DEBUG = false 存储在一个分支的文件中,并将 DEBUG = true 存储在另一个分支中吗?如何实现?

背景

我有各种远程存储库托管在 bitbucket 上。我在 Win8 上使用 SourceTree 将远程存储库克隆到我的笔记本电脑上。我为开发、功能、发行等创建不同的分支(遵循A successful Git branching model,不管好坏)。

我有一个名为 Dbug.java 的 Android Java 类,其中包含一个布尔值,用于在我的代码中打开/关闭各种调试日志记录、模拟等功能。

public static final boolean DEBUG = false;

在我的“生产”(master)分支上,我希望这个值为false,在我的功能分支上,我希望它为true

  • 使用过滤器可以实现这个目标吗?还是我已经误解了用例?
  • 我不确定过滤器是否在同一本地托管的存储库的两个分支之间起作用,或者过滤器只在两个不同的存储库之间起作用。

创建过滤器

在本地工作时,我切换到生产分支。我创建了一个名为debug_flag.txt的测试文件,其内容如下:

// false on production branch
// true on other branches
DEBUG = false;

我在本地仓库的根目录下创建了一个名为.gitattributes的文件,并向其中添加了过滤器引用:

debug_flag.txt filter=debug_on_off

我更新了.git/config文件,加入了过滤器定义:

[filter "debug_on_off"]
    clean = sed -e 's/DEBUG = true/DEBUG = false/'
    smudge = sed -s 's/DEBUG = false/DEBUG = true/'
  • 在我的理解中,这应该确保我的文件在生产环境中始终具有false值,但在我从生产分支分离时具有true值。
  • 这个理解正确吗?

测试过滤器

我使用以下命令创建了一个名为test的新分支:

git checkout -b test

我检查了我的文件内容:

$ cat debug_flag.txt

// false on production branch
// true on other branches
DEBUG = false;
  • 我希望在文件中看到值为true
  • "smudge" 过滤器不应该在我检出文件时运行吗?

我向文件添加了一行,并进行了提交。然后我切换回生产分支,这就是事情变得奇怪的地方。

如果我在 SourceTree 中查看文件,则自创建该分支以来,此分支上没有任何更改。这是我所期望的,因为唯一的更改是在不同的分支上进行的。

如果我在终端或 Notepad++ 中查看文件,则会看到我的值已更改:

$ cat debug_flag.txt

// false on production branch
// true on other branches
DEBUG = true;

我还没有将测试分支上的更改合并到生产分支上,也没有在生产分支上进行提交,但是文件已经发生了变化

  • 看起来在这个分支内已经对文件运行了smudge过滤器,但是跨分支还没有运行。

我缺少一个关键的部分,希望有经验的人能够发现这个简单的问题。

我打赌这是一个简单的概念误解。

请提示任何缺失的信息......


基于VonC的回复更新

设置基本过滤器效果很好。在config文件中定义了过滤器:

[filter "debug_on_off"]
    clean = sed -e 's/DEBUG = true/DEBUG = false/'
    smudge = sed -s 's/DEBUG = false/DEBUG = true/'

创建新分支可以解决false -> true的问题,合并回来会将true -> false。

将更改限制在生产(主)分支上需要具有所在分支意识的自定义脚本。因此,config文件变为:

[filter "debug_on_off"]
    clean = ./scripts/master_clean.sh
    smudge = ./scripts/master_smudge.sh

master_clean.sh:

#!/bin/sh
branch=$(git rev-parse --symbolic --abbrev-ref HEAD)
if [ "master" = "$branch" ]; then
    sed -e s/DEBUG = true/DEBUG = false/ $1
else
    cat $1
fi

master_smudge.sh:

#!/bin/sh
branch=$(git rev-parse --symbolic --abbrev-ref HEAD)
if [ "master" = "$branch" ]; then
    sed -e s/DEBUG = false/DEBUG = true/ $1
else
    cat $1
fi

目前,我遇到了一个问题,SourceTree看到的内容与Notepad++显示的调试文件内容不一致。 SourceTree显示有更改,但Notepad++没有。

我接受VonC的答案,因为它回答了我提出的基本问题。

然而,我可能会实现我编写的解决方案,因为它以一种更简单(对我来说)的方式解决了我尝试解决的根本问题:在不同的分支上保留不同的配置文件。


你可能想在$1周围添加引号,以支持带有空格的文件。 - phk
你的配置文件里面不是缺少了%f吗?(我不确定是否需要加引号,如果需要的话,它们需要被转义,因为当解析配置文件时,git本身也会解释它们) - phk
@phk 我在一年前就放弃了这个。现在我手动完成它(当然很痛苦)。但如果你有时间并且认为你有一个正常工作的解决方案,请随意发布。从我所发现的来看,git不支持此功能,我相信这是“按设计要求”的。 - Richard Le Mesurier
我在那个时候也放弃了,林纳斯的话语促使我在构建脚本中做出这样的事情。 - phk
5个回答

9

我希望在文件中看到true的值。

你刚刚创建了一个新的分支,但没有检出它的内容(因为它的内容与你所在的分支相同)。

要强制运行smudge,请在仓库顶部执行以下操作:

git checkout HEAD --

我尚未将测试分支的更改合并到生产分支,也没有在生产分支上进行提交,但文件已更改。这就是内容过滤驱动程序的概念:它修改内容,而不影响git状态(仍将修改后的文件报告为“未更改”)。为了使一个模糊匹配在每个分支上起不同的作用,建议调用一个脚本,该脚本首先查看当前分支的名称。请参见我的旧回答“最佳实践-Git+构建自动化-保持配置分离”中的示例。
#!/bin/sh
branch=$(git rev-parse --symbolic --abbrev-ref HEAD)

谢谢。我以为我之前已经看过你所有关于这个话题的回答了,但我错过了这一个。那么这些过滤器与分支无关?这让我感到很困惑。难道工作副本始终是未提交版本,而存储库中的副本(不是工作副本)则始终是干净的吗?但我们不是基于“干净”的版本构建的,因此才有了指向其他答案的链接…? - Richard Le Mesurier
1
默认情况下,smudge脚本不知道分支。如果.gitattributes在一个分支中声明了它,但在另一个分支中没有.gitattributes,那么它可能与分支有关。但是,如果它在两个分支中都声明了,那么您需要在smudge脚本中添加检测步骤。 - VonC
@RichardLeMesurier 目标不是拥有两个不同的.gitattributes文件,而是拥有一个智能到足以根据其执行环境(如当前分支)做出正确决策的smudge脚本。 - VonC
我下一步会处理这个问题。刚刚在回复你的评论时说过:“如果.gitattributes在一个分支中声明了,而另一个分支中却没有.gitattributes文件,那可能就与分支有关。”也许这是可能的,但现在我无法处理。 - Richard Le Mesurier
@RichardLeMesurier 我会看一下你的方法。感谢你的反馈。 - VonC
显示剩余4条评论

3

VonC的建议回答了我提出的确切问题,但是我无法解决最终细节(根据我对问题的更新)。这个答案给出了我做事情的细节。


更新

以下方法适用于第一次合并。但是之后就不再起作用了。我将其留在这里,因为它代表了我目前调查的状态。

似乎合并驱动程序不再被调用。

还尝试了相关问题的各种修改,例如使用exit 0touch %A或自定义脚本合并驱动程序(https://dev59.com/jHNA5IYBdhLWcg3whuR_#930495),而不是下面所示的true


我找到了一个解决方法,使用自定义的合并策略来解决根本问题,该问题是:
  • 我希望我的构建分支中的构建文件始终设置为关闭所有调试值。
  • 这可以防止意外发布具有模拟设置、本地主机设置、开启日志等的产品。

我基于以下问题的信息进行了如下操作:.gitattributes和单独的文件合并策略

1) 在.git/config文件中定义一个自定义合并驱动程序,如下所示:

[merge "ours"]
    name = "Keep ours merge"
    driver = true

我不确定这一步是否必需,但似乎它可能是某些(旧的?)系统上错误的解决方法。
(有关详细信息,请参见:https://dev59.com/eW035IYBdhLWcg3wW-z1#13000959
2) 在 build/production/pristine 分支中设置一个 .gitattributes 文件,以便特殊的调试标志使用上述合并策略。
因此,使用我问题中的文件,进入 "production" 分支,并将以下行添加到 .gitattributes 文件中:
debug_flag.txt merge=ours

每次合并到“生产”分支时,git将查找定义为“ours”的合并策略,并防止debug_flag.txt被覆盖。
3)在另一个分支上,设置您的.gitattributes文件,而不需要自定义合并策略。
4)配置过程的最后一步(但很重要)是在所有分支中正确设置debug_flag.txt文件,并提交更改到每个分支。
现在您应该有2个分支,每个分支都包含不同版本的.gitattributes和debug_flag.txt文件。这确保了每次合并时都会出现冲突。
如果没有冲突,则不会调用自定义的“ours”合并策略,文件可能会被覆盖。
(详情请参见:https://dev59.com/eW035IYBdhLWcg3wW-z1#16739788
5)最后将新分支合并回“生产”。由于步骤3和4,您将遇到合并冲突。解决冲突,使2个分支保持其差异。提交更改。
这两个分支之间的所有未来合并都将无缝地忽略debug_flag.txt文件。
这样做可以实现在不同分支上拥有两个不同的配置文件,以便轻松地将调试代码与生产代码等分离。这似乎是一个常见的用例,在这个论坛上有许多相关的问题,但我还是花了几天时间才搞定。

有趣的合并驱动程序使用,比我的答案更精确。+1 - VonC
@VonC - 谢谢,但是要感谢我链接的其他人。不过我确实想以易于理解的方式记录下来。我正在忙着根据你的答案开发一个解决方案。目前看起来很不错。 - Richard Le Mesurier
今天早些时候,这种技术在我这里出现了故障 - 我将不得不进行更多测试,以确定是我做错了什么,还是我的答案中存在错误。 - Richard Le Mesurier

3
看看expandr。这是一个脚本,使用smudge/clean可以在不同的分支上设置不同的配置。基本上就是你最初要求的内容。
现在最大的问题就是,在切换分支后有时我需要执行git checkout HEAD -- "$(git rev-parse --show-toplevel)"来正确地清理工作目录。但有时候它似乎可以正常工作;我还没有找出原因。我认为可能与“merge renormalize”被打开有关,导致一些问题?我不确定。(我已经打开了它。)
另一个注意点是,您必须使用merge=ours保护每个分支的.gitattributes文件本身,方法是添加一行.gitattributes merge=ours并为其打开驱动程序(如您所提到的)。这里的注意点是,在创建每个单独的分支之后,现在必须进入每个.gitattributes文件并修改每个文件(我建议现在添加注释,例如#touched for merge=ours on master#touched for merge=ours on test branch等,这样您将记住它的存在原因)。您必须这样做,因为merge=ours仅在文件在进入分支时已被更改,并在其父分支上之后被更改后,才会保护文件免于在合并中变成传入分支的版本。(请记住 git 处理的是更改,而不是文件。)

看起来不错 - 我已经标记了这个,等我有时间尝试一下。你额外的 checkout 命令可能是我遇到问题的最终解决方案。 - Richard Le Mesurier

0
最直接的解决方案是更新你的 makefile 文件,检查当前正在使用哪个分支。如果该分支属于已命名列表,则定义一个新的构建参数 -DDEBUG=true 或 -DDEBUG=false,否则不定义。
请参见 如何编程确定当前选中的 Git 分支
branch_name=$(git symbolic-ref -q HEAD)
branch_name=${branch_name##refs/heads/}
branch_name=${branch_name:-HEAD}

PROD_BRANCHES := master \
                QA
debug_flag=
ifneq ($(filter $(branch_name),$(PROD_BRANCHES)),)
    debug_flag="-DDEBUG=true"
endif
ifeq($debug_flag,)
    debug_flag="-DDEBUG=false"
endif

0

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