git:如何在分支之间进行合并,同时保留某个分支独有的某些变更集?
对于那些将绝对路径和数据库凭证硬编码到 Web 应用程序中多个随机位置的人来说,地狱里有一个特殊的地方。 可悲的是,在他们下地狱之前,他们正在地球上造成严重破坏。 我们必须处理他们的代码。
我必须对此类 Web 应用程序之一执行一些小更改。 我创建一个新分支 features
,并执行全局查找和查找。 替换以更新我的本地环境的路径和凭据。 我承诺这一点。 我还将其标记为本地
。
我高兴地跳入危险的黑客忏悔,在经历了数百个令人困惑的补丁之后,我想将我的 features
更改合并到 master
分支,但我不想要一个 local
提交合并。
今后,我将在 master
和 features
之间来回合并,并且我希望 local
保留在 features 中
,并且永远不会出现在master
中。
理想情况下,我希望这一切都能神奇地发生,并且使用尽可能少的有趣参数等。
有没有一种我所缺少的简单明显的方法可以做到这一点?
我可以想到几个,但它们都要求我记住我不想要那个提交。 这绝对不是我的强项。 尤其是对于这种被黑客攻击得很差的程序。
如果做不到这一点,我对更复杂的、手动的方式来处理这种情况感兴趣。
There's a special place in hell for people who hardcode absolute paths and database credentials into multiple random places in web applications. Sadly, before they go to hell they're wreaking havoc on Earth. And we have to deal with their code.
I have to perform a few small changes to one of such web applications. I create a new branch features
, and perform a global find & replace to update the paths and credentials to my local environment. I commit that. I also tag this as local
.
I merrily leap into perilous hacking penitence, and after a perplexing hundred patches, I want to merge my features
changes into the master
branch, but I do not want the one local
commit to be merged.
Onwards, I'll be merging back and forth between master
and features
, and I'd like local
to stay put in features
, and never ever show up in master
.
Ideally, I'd like all this to happen magically, with as little funny parameters and whatnot as possible.
Is there a simple obvious way to do it that I'm missing?
I can think of a couple, but they all require me to remember that I don't want that commit. And that's definitely not my forte. Especially with such poorly hacked programs.
Failing that, I'm interested in more convoluted, manual-ish ways to handle the situation.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我对这个问题的解决方案使用 rebase 而不是 merge
从这样的提交树开始:
$ git rebase --onto master local dev
$ git checkout master
$ git merge dev
$ git rebase --onto master master local
$ gitbranch -f dev当地的
My solution to this problem uses rebase rather than merge
Starting with a commit tree like this:
$ git rebase --onto master local dev
$ git checkout master
$ git merge dev
$ git rebase --onto master master local
$ git branch -f dev local
您可以使用 gitcherry pick 仅合并您选择的补丁。 只需挑选除本地提交之外的所有提交到主分支即可。
You can use git cherry pick to only merge the patches you select. Just cherry pick every commit except for the local one over to the master branch.
技术 (Git) 解决方案将使用
git 属性
,使用属性合并。您会在SO问题中找到“我如何告诉 git 始终选择本地版本来进行特定文件上的冲突合并?”使用此类属性的示例,以强制保留合并到给定分支时某些文件的本地版本。
不要忘记您可以关联任何类型的脚本来通过 git 属性来管理这些合并。 其中包括一个脚本,该脚本能够将您想要的更改保留在本地,同时合并其余部分。 编写这样的“合并管理器”比较复杂,但它是一种实现临时自动化解决方案的方法。
一种技术含量较低的解决方案是将配置值与配置文件分开:
脚本用于将实际配置文件中的名称替换为给定环境所需的配置值文件之一的值。
A technical (Git) solution would be using
git attributes
, using the attribute merge.You will find in the SO question "How do I tell git to always select my local version for conflicted merges on a specific file?" an example of using such an attribute, to force keeping the local version of certain files when merging to a given branch.
Do not forget you can associate any kind of script to manage those merges through
git attributes
. That include a script able to keep changes you want local, while merging the rest. It is more complicated to write such a "merge manager", but it is a way toward an ad-hoc automated solution.A less-technical solution would be to separate the configuration values from the configuration files:
A script is used to replace the name in the actual config file by the values of one of the config values files needed for a given environment.
好的。 这并不能保证每次都有效,但类似的事情可以工作(在这种情况下,无论如何你都会有一个必须解决的冲突更改):
在合并到时 master:
rebase -i master
并将仅限本地的更改移至补丁链的END。合并你的本地分支-1:
git merge local^
这将使你的 master 拥有本地上除最后一个之外的所有更改。
如果您有多个 local=only 更改,我建议您在变基期间将它们压缩在一起。
ok. this is not guaranteed to work every time but something like this can work (and in the cases it wont you will have a conflicting changes anyway that has to be resolved):
when doing merge to the master:
rebase -i master
from your branch and move the local-only change to the END of the patch chain.merge your local-branch -1:
git merge local^
This will leave you with master having all the changes on the local except for the last one.
If you have multiple local=only changes, I suggest you squash them together during rebase.
我会针对 master 进行交互式变基,并将您的路径名修复提交移至末尾。 然后,您可以合并到该点。 只要继续将您的特殊承诺坚持到底即可。
您可能还会发现这个储藏室很有用。 您可以将它们隐藏起来,而不是实际提交路径名修复。 如果尝试这种方法,您可能需要查看如何反向应用存储上的问题。
I would do an interactive rebase against master and move your path-name-fixup-commit to the end. Then, you can merge up to the that point. Just keep moving your special commit to the end.
You may also find the stash useful. Instead of actually committing the path name fixups you could stash them away. If try this approach you may want to check out the question on How to reverse apply a stash.
好吧,因为到目前为止还没有答案提供直接的解决方案,所以我假设我想做的事情是不可能的,并添加到一堆偶尔有用的解决方案中:
如果您总是在
功能
上进行开发分支,然后您可以将features
合并到master
,然后在master
中,git revert local
。 (其中local
是引用您为本地环境自定义路径等的提交的标记。)现在您绝不能将
master
合并到features
,因为这也会合并反向local
提交。在这种情况下,
master
变成了一种部署分支,只接收来自其他分支的合并。 (理想情况下,仅来自features
分支。)这很容易走下坡路,只需在工作流程中添加另一个开发人员,事情就会变得非常混乱。 仍然可以通过使用显式合并策略来解决,但这通常很痛苦。
Well, because no answer so far provided a straightforward solution, I'll assume what I want to do is impossible, and add to the pile of occasionally useful solutions:
If you're always developing on the
features
branch, then you can mergefeatures
tomaster
, and then, inmaster
,git revert local
. (Wherelocal
is the tag referencing the commit where you customized the paths, etc for your local environment.)Now you must never merge
master
intofeatures
, because that would merge the reverselocal
commit too.In this case
master
becomes sort of a deployment branch, only ever receiving merges from other branches. (Ideally, only from thefeatures
branch.)This goes downhill very easily, just add another developer to the workflow and things get really messy. Still can be worked around by using explicit merge strategies, but it's generally a pain.
我不知道这是否可行,但是:
MAKE_LOCAL
MAKE_REMOTE
gitcherry-pick MAKE_REMOTE
(或使用git diff
和patch
)gitcherry-pick MAKE_LOCAL
(或使用git diff
和patch
)我认为有一种更好的方法以这种方式转换文件,但我不记得了(如果您可以从 RubyConf 找到 shacon 的 git 演示文稿,并且可以浏览 800 张幻灯片,其中有一些很棒的示例)。
I don't know if this would work, but:
MAKE_LOCAL
MAKE_REMOTE
git cherry-pick MAKE_REMOTE
(or usegit diff
andpatch
)git cherry-pick MAKE_LOCAL
(or usegit diff
andpatch
)I think there is an even better way of transforming files in this manner, but I can't recall (if you can find shacon's git presentation from RubyConf, and can wade through 800 slides, it's in there with some great examples).
就我个人而言,如果我必须做这样的事情,并且由于某种原因无法重构凭证,我会再添加两个分支,最终得到类似于以下的安排:
master
:您从 master 继承的localcred
: 分支的原始代码,只添加一个补丁,将所有凭证更改为您本地所需的凭证。 此后将此分支视为只读(并可能添加一个钩子以防止意外提交)。
feature
:来自 master 的分支,所有修复都放在这里(可能添加一个钩子以防止与 localcred 中的补丁合并)local
:一个分支(不是标签!)它将作为localcred
的一个分支开始,然后在您需要运行单元测试时合并功能。 所有测试都从这里进行,但这里不进行任何开发。 另外,这个分支是一次性的,因为你可能想在feature
内部进行变基,处理结果的最快方法是删除分支local
,然后对其进行分支在运行测试之前再次从localcred
合并feature
。 这在我的工作流程中可能是一个足够常见的操作,我会构建一个别名,只需敲击几下键盘即可重复执行此操作,但我对 Git 分支的可处置性感到非常困扰,这让一些人感到害怕看着我,所以YMMV。当您认为您的修复已准备好发布时,您可以对
feature
进行最终变基以清理历史记录,转储并重新创建local
以进行最终测试,合并feature
到master
中,一旦被上游接受,将master
合并到localcred
中,并将您的凭证补丁重新设置到顶部,然后转储并重新创建本地
和功能
并重新玩游戏。如果您想快速测试大量代码的微小变化,而不必每次都提交和合并,请签出
local
,进行更改,直到您满意为止,提交并立即从中挑选将local
放入feature
,然后删除并重新创建 local。这能满足您的需求吗?
Personally, if I had to do something like this and was for whatever reason prevented from refactoring credentials as I go, I'd add two more branches, ending up with an arrangement similar to the following:
master
: the original code you inheritedlocalcred
: branch from master, and add just the one patch that changes all the credentials to what you need locally. Treat this branch as read-only hereafter (and possibly add a hook to prevent accidental commits).feature
: branch from master, and all fixes go here (and possibly add a hook to prevent merging with the patch in localcred)local
: a branch (not a tag!) that will start out as a branch oflocalcred
, and then merge feature whenever you need to run your unit tests. All testing happens from here, but no development happens here. In addition, this branch is disposable, because you might want to rebase inside offeature
, and the fastest way to deal with the result will be to delete branchlocal
, branch it again fromlocalcred
and mergefeature
before running your tests. This is likely to be a common enough operation in my workflow that I'd build an alias to do it repeatedly in just a few keystrokes, but I work the hell out of the disposability of Git branches, which kind of freaks out some people who watch me, so YMMV.When you think your fixes are ready for publication, you do your final rebase of
feature
to clean up the history, dump and recreatelocal
for your final test, mergefeature
intomaster
, and once that's accepted upstream, mergemaster
intolocalcred
and rebase your credential patch to the top, then dump and recreatelocal
andfeature
and play the game all over again.If you want to rapidly test a large set of tiny variations of code without having to commit and merge each time, checkout
local
, make your changes until you're happy, commit, and immediately cherry-pick fromlocal
intofeature
, then drop and recreate local.Does that satisfy your needs?
这个问题是老问题了,但我仍然没有找到好的答案。 目前我面临着同样的问题,下面是我的解决方法:
我的本地存储库中有两个分支:
master
和local_settings
。从
master
切断local_settings
分支后,我在那里提交了所有本地路径,没有标记,也没有试图记住它们。在本地开发期间,我切换到
local_settings
分支,因此我可以使用本地路径运行应用程序。 但是,当需要提交时,我会存储当前状态并切换到master
分支。 然后我弹出隐藏的变更集并将其提交到master
中。 最后一步是切换回local_settings
,从master
合并并继续开发。回顾一下:我只将更改提交到
local_settings
分支,这些更改将保留在本地并且永远不会进入 master ; 并且不会从local_settings
合并到master
。现在假设我需要对之前添加的本地路径的文件添加“良好”修改,但在
master
分支中需要“良好”修改。当工作副本是
local_settings
的头部时,我会进行更改,将其隐藏并查看master
。 尽管我已经在master
上,但存储保留了一个与local_settings
相关的变更集。git stash pop
将隐藏的变更集应用到工作副本,并最终得到相对于主版本的差异,但仅限于最近的修改,不包括之前添加的且不属于工作副本的本地路径。最近隐藏的变更集。 因此,可以在不弄乱 master 分支中的路径的情况下提交它。 然后再次从master
合并到local_settings
。The question is an old one, but I still have not found a good answer. Currently I am facing the same issue and below is my workaround to deal with it:
There are two branches in my local repo:
master
andlocal_settings
.Having cut off the
local_settings
branch frommaster
I committed there all local paths, not tagging and not trying to remember them.During local development I am switched to the
local_settings
branch, so I can run an application using local paths. But when it is time to commit I stash a current state and switch to themaster
branch. Then I pop the stashed changeset and commit it intomaster
. And the final step is to switch back tolocal_settings
, merge frommaster
and continue development.To recap: I commit into the
local_settings
branch only changes that will stay locally and will never go into master; and no merges fromlocal_settings
tomaster
.Now let's say I need to add a "good" modification to a file with a local path added earlier, but the "good" modification is wanted in the
master
branch.I do my changes when the working copy is a head for
local_settings
, stash it and check outmaster
. The stash keeps a changeset, that is relative tolocal_settings
, although I am onmaster
already.git stash pop
applies the stashed changeset to the working copy and ends up having a diff relative to master, but only with the recent modification excluding the local path that had been added earlier and was not a part of the recent stashed changeset. Hence it can be committed without messing paths in themaster
branch. Afterwards again merge frommaster
tolocal_settings
.