假设我检查了我的“主”分支。我已经对“master”进行了一些生产更改,现在我想将我的“实验”分支重新设置到最新的 master 上。但是,我想在不修改工作副本中的任何文件的情况下执行此操作。本质上,我希望所有的魔法都发生在 .git 目录中,而不触及工作副本。
如果不是为了“不要修改我的工作副本”要求,这只是一个做的问题:
# current branch is master
git checkout experimental
git rebase master
git checkout master
我真正的问题是这会修改我的工作副本中的时间戳,即使我最后检查完全相同的内容我开始于。一旦我运行“git checkoutexperimental”,任何在experimental分支中包含更改的文件都会将其mtime设置为当前时间——自上次我重新基于experimental以来在master中更改的任何文件也会如此。因为时间已经改变,像构建工具这样的东西会认为他们需要再次做一些工作,尽管当我完成时,文件的内容实际上并没有改变。 (就我而言,如果项目文件的时间戳发生更改,Visual Studio 认为它需要花费大量时间卸载和重新加载项目。)我想避免这种情况。
有没有一种方法可以一步完成上述所有操作,而无需修改工作副本中的任何内容 (假设变基期间没有冲突)< /em>?
(如果存在有冲突,我的偏好是显示错误,然后中止整个操作,而不修改任何时间戳。但这只是我的偏好,不是一个硬性要求——我不知道什么都是可能的。)
当然,我可以编写一个脚本来捕获 mtime,运行 git,然后重置 mtime;但 Git 似乎已经有一种方法可以在不影响工作副本的情况下完成诸如 rebase 之类的事情,因为 rebase 实际上是关于增量,而不是文件的实际内容。
Suppose I have my "master" branch checked out. I've committed some production changes to "master", and now I want to rebase my "experimental" branch onto the latest master. But, I want to do this without modifying any files in my working copy. Essentially, I want all the magic to happen inside the .git directory, without touching the working copy.
If not for the "don't modify my working copy" requirement, this would just be a matter of doing:
# current branch is master
git checkout experimental
git rebase master
git checkout master
My real problem is that this modifies timestamps in my working copy, even though I'm ending by checking out the exact same content I started with. As soon as I run "git checkout experimental", any files that contain changes in the experimental branch will get their mtime set to the current time -- and so will any files that were changed in master since the last time I rebased experimental. Because the mtimes have changed, things like build tools get the idea that there's work they need to do again, even though, by the time I'm done, the files' contents haven't actually changed. (In my case, it's that if a project file's timestamp changes, Visual Studio thinks it needs to spend a lot of time unloading and reloading the project.) I want to avoid that.
Is there a way to do all of the above in one step, without ever modifying anything in the working copy (assuming there are no conflicts during the rebase)?
(If there are conflicts, my preference would be to show the error and then abort the entire operation, without ever modifying any timestamps. But that's just my preference, not a hard requirement -- I don't know what all is possible.)
Of course I can write a script to capture the mtimes, run git, and then reset the mtimes; but it seems likely that Git would already have a way to do things like rebase without bothering the working copy, since the rebase is really about the deltas, not the files' actual contents.
发布评论
评论(8)
从 git 2.5 开始,更好的解决方案是使用第二个工作树。
就是这样。之后,如果需要,您可以
rm -rf secondary-copy
,或者保留它以供将来进行更多变基。Since git 2.5, an even better solution is to use a second worktree.
And that's it. Afterwards, you can
rm -rf second-copy
if you want, or keep it for more rebases in the future.不幸的是,这是不可能的(如果不创建工作副本的可修改副本 - 另请参阅 Petr 的答案),因为 git 执行所有合并-y 工作树上的操作(真正的合并、精选、变基、补丁应用)。之前多次提到过这一点,例如知识渊博的 Jakub Narębski 的回答之一:
是的,这是一个设计决策,但这是一个非常可以理解的决策 - 构建尝试在内存中合并所需的所有结构,然后一旦发生冲突,将所有内容转储到内存中,这会是一件苦差事。工作树,而您首先可以简单地在工作树中完成它。 (我不是 git 开发人员;不要将此视为绝对完整的事实。可能还有其他原因。)
我的建议是,不要编写脚本来执行所有 mtime 操作,而是简单地克隆存储库,执行克隆中的变基,然后将其推回到原始存储库中:
当然,这是假设您的原始存储库中没有签出实验性的。如果是,您可以执行类似 git fetch ../project-for-rebaseexperimental; 的操作,而不是推送。 git reset --hard FETCH_HEAD 或更具可读性,
git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental
。这自然会触及原始实验分支和重新基础实验分支之间不同的任何文件,但这绝对是正确的行为。 (当然,这不是您给出的示例,但我希望这些说明是通用的!)This is unfortunately impossible (without creating a modifiable copy of the working copy - see also Petr's answer), because git performs all merge-y operations (real merges, cherry-picks, rebases, patch application) on the work tree. This is mentioned several times before, for example in one of the knowledgeable Jakub Narębski's answers:
Yes, it's a design decision, but it's a pretty understandable one - it'd be a bit of a chore to build up all the structure necessary to attempt a merge in memory, then as soon as it hits a conflict, dump everything into the work tree, when instead you could simply do it in the work tree in the first place. (I'm not a git developer; don't take this as absolute complete truth. There could be other reasons.)
My suggestion, rather than writing a script to do all that mtime manipulation, would be simply to clone the repository, perform the rebase in the clone, then push it back into your original repository:
That of course assumes that experimental isn't checked out in your original repo. If it is, instead of the push, you'd do something like
git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD
or more readable,git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental
. That will naturally touch whatever files differ between the original and rebased experimental branches, but that's definitely correct behavior. (This wasn't the example you gave, of course, but I want these instructions to be general!)我创建了一个小脚本来在 Linux 上执行此操作。它基于 Jefromi 的答案和一些补充(主要是设置替代项,以便不复制对象数据库,并且仅拉取所需的分支)。你们中的一些人可能会发现它很有用:
https://github.com/encukou/bin/blob/master/oot-rebase
如果它不能完全满足您的要求,欢迎提出拉取请求:)
I've created a small script to do this on linux. It's based on Jefromi's answer and a few additions (mainly, setting up alternates so the object database isn't copied, and only pulling the needed branches). Some of you may find it useful:
https://github.com/encukou/bin/blob/master/oot-rebase
If it doesn't do quite what you like, pull requests are welcome :)
与创建存储库的克隆类似,我发现它是使用多个工作目录来做这样的事情要整洁得多。此外,克隆会使用大量磁盘空间,而这几乎不会使用任何磁盘空间。
https://github.com/git/git/ blob/master/contrib/workdir/git-new-workdir
您可以像这样创建一个新的工作目录:
然后您可以将其视为克隆,除了原始工作目录中的获取和提交将反映在这里(尽管不在工作分支上而无需重新签出)。唯一的例外是如果您有子模块,因为默认情况下这些子模块是为每个工作目录单独完成的。坦率地说,我从未研究过这一点,因为我尝试避免使用子模块。
所以基本上只是:
不完全是一个命令,但非常简单。
与下面的存储方法相比,此方法(如克隆方法)的优点是,如果您当前正在工作目录中执行代码(或由某些进程使用),则该代码不会被中断。
此处未提及的另一个选项是在当前工作目录中执行此操作,但存储更改,以便您可以立即恢复工作目录状态。
如果您有任何新文件,请确保使用
stash -u
,否则它们将不会被隐藏。再说一次,不是一步,但非常干净和简单。Similar to creating a clone of your repository, I find that it's much tidier to make use of multiple workdirs to do things like this. Also a clone will use a lot of disk space, this will use almost none.
https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir
You create a new workdir like this:
Then you can treat that as if it was a clone except that fetches and commits in your original workdir will be reflected here (although not on the working branch without recheckout). The only exception to this is if you have submodules as those are done separately for each workdir by default. Frankly I've never looked in to that because I try and avoid submodules.
So basically just:
Not exactly a single command, but pretty simple.
An advantage of this approach (like the clone approach) over the stash approach below is that if you have code currently executing (or otherwise being used by some processes) from your working directory, it isn't interrupted.
The other option which isn't mentioned here is to do it in your current working directory, but stash your changes so that you can then instantly restore your working directory state.
Make sure to use
stash -u
if you have any new files as otherwise they will not be stashed. Again, not one step, but pretty clean and simple.正如其他人所说,在不触及工作目录的情况下不可能对分支进行变基(即使是建议的替代方案,例如创建新的克隆或工作树也无法改变这一事实;这些替代方案确实不会触及您当前的工作目录,但只能通过创建一个新的工作树)。
对于要更新的分支要基于当前工作树(或其父级)重新建立基础的特殊情况,可以“重新建立”另一个分支,而无需不必要地接触文件。
如果您有一个 git 工作流程,其中您正在处理许多分支,这些分支都是从主“master”分支(定期更新到远程主分支)分支,那么这种特殊情况经常会发生。
为了说明这一点,假设一个具有以下结构的 Git 存储库:
为了举例说明,我们假设“OtherBranch”是从“master”分支出来的,并且您当前的工作树也基于“master”。
您的工作流程通常从使用远程版本更新本地主分支开始......
然后您摆弄当前分支并进行一些耗时的编译。最终,您决定在
OtherBranch
上工作。此OtherBranch
应该基于master
重新构建(最好使用最少的文件系统操作)。以下部分将展示如何操作。变基其他分支(参考示例 - 不要这样做)
以下解决方案是 git 方法:
这样做的缺点是第一个命令更改了当前工作树的日期,即使文件将通过以下方式恢复第二个命令。
以最少的更改重新确定其他分支的基础
为了最大限度地减少触及的文件数量,您需要签出到新的基础分支,然后使用 OtherBranch 中应用所有额外提交“https://stackoverflow.com/questions/1670970/how-to-cherry-pick-multiple-commits/3933416#3933416">
gitcherry-pick
。在执行任何操作之前,您需要识别
OtherBranch
中的提交。git log OtherBranch
显示 OtherBranch 上的提交(如果您尚未更改OtherBranch
,则主要有用)git reflog
显示对您的分支的更改本地存储库(如果您已经更新了分支并犯了错误,则很有用)。在当前示例中,您将发现
OtherBranch
上的最后一次提交是commitE
。您可以使用 git log commitE 来查看之前的提交列表(或者如果您想要更短的列表,则使用 git log --oneline commitE )。如果您查看列表,您将看到基本提交是commitC
。现在您知道基本提交是
commitC
并且最后一次提交是commitE
,您可以按如下方式重新设置 OtherBranch 的基数(从以前的“master”到新的“master”) :或者(如果您想在替换
OtherBranch
之前成功完成“变基”):为什么这样做有效?
git 中的分支变基需要将当前分支切换到要更新的分支 (
OtherBranch
)。使用
git rebase
工作流程,会发生以下情况:OtherBranch
(可能是从一个非常旧的基础分支分支出来的)。步骤 1 和步骤 3 涉及许多文件,但最终许多涉及的文件实际上并未发生更改。
我的方法将步骤 1 和 3 合并为步骤 3,因此接触的文件数量最少。唯一受影响的文件是:
OtherBranch
中的提交更改的文件。As others have said, it is not possible to rebase a branch without touching the working directory (even the suggested alternatives such as creating a new clone or worktree cannot change this fact; these alternatives do indeed not touch your current working directory, but only by creating a new worktree).
For the special case where the branch that you want to update is to be rebased on the current working tree (or a parent thereof), it is possible to "rebase" the other branch without unnecessarily touching files.
This special case often happens if you are having a git workflow where you are working on many branches that are all branched from the main "master" branch (which is regularly updated to the remote master branch).
To illustrate, assume a Git repository with the following structure:
For the sake of the example, let's assume that "OtherBranch" is branched off "master", and that your current working tree is also based on "master".
Your workflow typically starts with updating your local master branch with the remote version...
... and then you fiddle with the current branch and do some time-consuming compilations. Eventually, you decide that you want to work on
OtherBranch
. ThisOtherBranch
should be rebased onmaster
(preferably with minimal filesystem operations). The following section will show how.Rebasing other branch (reference example - do NOT do this)
The following solution is the git way to do it:
The disadvantage of that is that the first command changes the dates of the current worktree, even though the files are going to be restored by the second command.
Rebasing other branch with minimal changes
To minimize the number of touched files, you need to check out to the new base branch and then apply all extra commits in
OtherBranch
on top of the base branch usinggit cherry-pick
.Before doing anything, you need to identify the commits in
OtherBranch
.git log OtherBranch
shows the commits on OtherBranch (mainly useful if you haven't changedOtherBranch
yet)git reflog
shows the changes to branches in your local repository (useful if you have already updated branches and made a mistake).In the current example, you will discover that the last commit on
OtherBranch
iscommitE
. You can see a list of commits before that withgit log commitE
(or if you want a shorter list,git log --oneline commitE
). If you look through the list, you will see that the base commit iscommitC
.Now you know that the base commit is
commitC
and the last commit iscommitE
, you can rebase OtherBranch (from its previous "master" to the new "master") as follows:Alternatively (if you want to successfully complete the "rebase" before replacing
OtherBranch
):Why does this work?
Rebasing branches in git requires one to switch the current branch to the branch that you want to update (
OtherBranch
).With the
git rebase
workflow, the following happens:OtherBranch
(potentially branched off a very old base branch).Step 1 and step 3 touch many files, but ultimately many of the touched files have not actually changed.
My method combines step 1 and 3 into step 3, and as a result the number of touched files is minimal. The only files that are touched are:
OtherBranch
.我也很喜欢它,但这对我来说没有希望:
http://git-scm.com/docs/git-rebase
I would also love it, but this leaves no hope to me:
http://git-scm.com/docs/git-rebase
Craig- P. Motlin 的 答案建议使用工作树。
现在,自从 Git 2.41(2023 年第 2 季度)确保使用一些子命令来阻止用户在链接到同一存储库的另一个工作树中使用的分支上工作时,这一点现在更加强大。
Craig- P. Motlin's answer suggests the use of worktree.
This is now more robust since Git 2.41 (Q2 2023) makes sure a few subcommands have been taught to stop users from working on a branch that is being used in another worktree linked to the same repository.
因此,您希望在签出该分支之前对该分支进行变基吗?我真的不明白其中的原因,因为如果您不签出该分支,您就无法对其进行操作。为什么要对一个你不工作的分支进行变基?进行结账,它会改变你的mtime,然后进行rebase。变基将触及已更改的文件,当然您需要重建它们。
然而,解决此问题的一个简单方法是使用其他工作树进行变基。只需将环境变量 GIT_WORK_TREE 设置为其他工作树即可。只是不要忘记让你的 HEAD 与你的工作树相匹配。
根据他所在的分支以及推送的内容,推送到非裸仓库可能是危险的。更好的解决方案是使用珍贵的工作树从存储库中获取。
示例:
`
orgbranch=$(git rev-parse HEAD)
mkdir /tmp/tmp_wd
cp -r !(.git) /tmp/tmp_wd
导出 GIT_WORK_TREE=/tmp/tmp_wd
git checkoutbranch1
git rebase master
git checkout $orgbranch
导出 GIT_WORK_TREE=
rm -rf /tmp/tmp_wd`
So you want a rebase done on for a branch before you checkout that branch? I really can't see the reason for that, since if you don't checkout that branch you can't work on it. Why do you want to rebase a branch that you don't work on? Do the checkout, it will change your mtime and then do the rebase. The rebase will touch files that are changed and of course you need to rebuild them.
However, a simple way to solve this is to use an other worktree for the rebase. Just set the enviroment variable GIT_WORK_TREE to an other worktree. Just don't forget to have your HEAD match your worktree.
Depending on which branch he is at and what's pushed, a push to a non-bare repo can be dangerous. A much better solution is to fetch from the repo with the precious worktree instead.
Example:
`
orgbranch=$(git rev-parse HEAD)
mkdir /tmp/tmp_wd
cp -r !(.git) /tmp/tmp_wd
export GIT_WORK_TREE=/tmp/tmp_wd
git checkout branch1
git rebase master
git checkout $orgbranch
export GIT_WORK_TREE=
rm -rf /tmp/tmp_wd`