我可以在不修改我的工作副本的情况下重新设置 Git 分支吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4913360/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can I rebase a Git branch without modifying my working copy?
提问by Joe White
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.
假设我检查了我的“主”分支。我已经对“master”进行了一些生产更改,现在我想将我的“实验性”分支重新设置为最新的 master。但是,我想在不修改工作副本中的任何文件的情况下执行此操作。本质上,我希望所有的魔法都发生在 .git 目录中,而不需要触及工作副本。
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.
我真正的问题是这会修改我的工作副本中的时间戳,即使我以检查与开始时完全相同的内容结束。一旦我运行“git checkout Experimental”,任何在实验分支中包含更改的文件都会将它们的 mtime 设置为当前时间——自上次我重新设置实验后在 master 中更改的所有文件也将设置为当前时间。因为 mtime 发生了变化,诸如构建工具之类的东西会认为他们需要再次做一些工作,即使当我完成时,文件的内容实际上并没有改变。(就我而言,如果项目文件的时间戳发生更改,Visual Studio 认为它需要花费大量时间卸载和重新加载项目。)我想避免这种情况。
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)?
有没有一种方法可以一步完成上述所有操作,而无需修改工作副本中的任何内容(假设在 rebase 期间没有冲突)?
(If there areconflicts, 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.
当然我可以写一个脚本来捕获mtimes,运行git,然后重置mtimes;但似乎 Git 已经有一种方法可以在不打扰工作副本的情况下执行 rebase 之类的操作,因为 rebase 实际上是关于增量,而不是文件的实际内容。
采纳答案by Cascabel
Is there a way to do all of the above in one step, without ever modifying anything in the working copy?
有没有一种方法可以一步完成上述所有操作,而无需修改工作副本中的任何内容?
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:
不幸的是,这是不可能的(不创建工作副本的可修改副本 -另请参阅 Petr 的回答),因为 git 在工作树上执行所有合并操作(真正的合并、樱桃选择、rebases、补丁应用程序)。之前多次提到过这一点,例如在知识渊博的 Jakub Nar?bski 的回答之一中:
There is no way that merge (or rebase) can work without touching the working directory (and index), as there can be merge conflicts that have to be resolved using working directory (and/or index).
合并(或变基)无法在不触及工作目录(和索引)的情况下工作,因为可能存在必须使用工作目录(和/或索引)解决的合并冲突。
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.)
是的,这是一个设计决定,但这是一个非常可以理解的决定 - 建立所有尝试在内存中合并所需的结构会有点麻烦,然后一旦遇到冲突,将所有内容转储到工作树,而不是你可以简单地首先在工作树中进行。(我不是 git 开发人员;不要将此视为绝对完整的事实。可能还有其他原因。)
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:
我的建议,与其编写脚本来执行所有 mtime 操作,不如简单地克隆存储库,在克隆中执行变基,然后将其推回原始存储库:
git clone project project-for-rebase
cd project-for-rebase
git branch experimental origin/experimental
git rebase master experimental
git push origin experimental
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!)
这当然假设您的原始存储库中未检出实验。如果是这样,而不是推送,你会做一些类似git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD
或更易读的事情,git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental
. 这自然会涉及原始和重新调整的实验分支之间的任何文件不同,但这绝对是正确的行为。(当然,这不是您给出的示例,但我希望这些说明具有通用性!)
回答by Craig P. Motlin
Since git 2.5, an even better solution is to use a second worktree.
从git 2.5 开始,更好的解决方案是使用第二个工作树。
A git repository can support multiple working trees, allowing you to check out more than one branch at a time.
一个 git 存储库可以支持多个工作树,允许您一次检出多个分支。
$ git worktree add ../second-copy experimental
$ cd ../second-copy/
$ git rebase master experimental
And that's it. Afterwards, you can rm -rf second-copy
if you want, or keep it for more rebases in the future.
就是这样。之后,您可以rm -rf second-copy
根据需要保留它,以便将来进行更多变基。
$ git rebase master experimental
回答by Petr Viktorin
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
我创建了一个小脚本来在 linux 上执行此操作。它基于 Jefromi 的回答和一些补充(主要是设置替代项,以便不复制对象数据库,并且只提取所需的分支)。你们中的一些人可能会发现它很有用:https: //github.com/encukou/bin/blob/master/oot-rebase
If it doesn't do quite what you like, pull requests are welcome :)
如果它没有做你喜欢的,欢迎请求请求:)
回答by dpwrussell
Update, since git 2.5 this answer is superseded by the in-built mechanism "worktree" which is basically the same. See above answer: https://stackoverflow.com/a/12481546/1499102
更新,自 git 2.5 起,此答案已被基本相同的内置机制“工作树”取代。见上面的答案:https: //stackoverflow.com/a/12481546/1499102
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
https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir
You create a new workdir like this:
你像这样创建一个新的工作目录:
git-new-workdir project-dir new-workdir branch
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:
所以基本上只是:
cd new-workdir
git checkout experimental
git rebase master
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.
与下面的 stash 方法相比,这种方法(如克隆方法)的一个优点是,如果您的工作目录中当前有代码正在执行(或以其他方式被某些进程使用),则它不会被中断。
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.
此处未提及的另一个选项是在您当前的工作目录中执行此操作,但存储您的更改,以便您可以立即恢复您的工作目录状态。
# current branch is master (with changes to working state)
git stash -u
git checkout experimental
git rebase master
git checkout master
git stash pop
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.
stash -u
如果您有任何新文件,请确保使用,否则它们将不会被隐藏。同样,不是一步,而是非常干净和简单。
回答by Rob W
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).
对于您要更新的分支要基于当前工作树(或其父树)进行重新定位的特殊情况,可以“重新定位”另一个分支而不会不必要地接触文件。
如果您有一个 git 工作流,其中您正在处理许多分支,这些分支都从主“master”分支(定期更新到远程 master 分支)分支,那么这种特殊情况经常发生。
To illustrate, assume a Git repository with the following structure:
为了说明这一点,假设一个具有以下结构的 Git 存储库:
repo
- commitA
- commitB
- commitC <-- master <-- OtherBranch based on master
- commitD <-- First commit in otherBranch
- commitE <-- Second commit in OtherBranch
- commitD <-- Unrelated commit in current working tree
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...
为了这个例子,我们假设“OtherBranch”从“master”分支出来,并且您当前的工作树也基于“master”。您的工作流程通常从使用远程版本更新本地主分支开始...
# Fetch commits from remote "origin" and update the master branch:
# If your current branch is identical to master
git pull origin master
# If your current branch has extra commits on top of master
git pull --rebase origin master
# If you don't want to touch your current branch
git fetch origin master:master
... and then you fiddle with the current branch and do some time-consuming compilations. Eventually, you decide that you want to work on OtherBranch
. This OtherBranch
should be rebased on master
(preferably with minimal filesystem operations). The following section will show how.
...然后你摆弄当前分支并进行一些耗时的编译。最终,您决定要从事OtherBranch
. 这OtherBranch
应该重新基于master
(最好使用最少的文件系统操作)。下面的部分将展示如何。
Rebasing other branch (reference example - do NOT do this)
重新定位其他分支(参考示例 - 不要这样做)
The following solution is the git way to do it:
以下解决方案是 git 方法:
git checkout OtherBranch
git rebase master # or git rebase origin/master
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 using git cherry-pick
.
为了最大限度地减少接触文件的数量,您需要检出新的基础分支,然后OtherBranch
使用git cherry-pick
.
Before doing anything, you need to identify the commits in OtherBranch
.
在做任何事情之前,您需要确定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).
git log OtherBranch
显示在 OtherBranch 上的提交(如果你还没有改变,主要是有用的OtherBranch
)git reflog
显示对本地存储库中分支的更改(如果您已经更新了分支并犯了错误,则很有用)。
In the current example, you will discover that the last commit on OtherBranch
is commitE
. You can see a list of commits before that with git 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 is commitC
.
在当前示例中,您会发现最后一次提交OtherBranch
是commitE
。您可以在此之前查看提交列表git log commitE
(或者,如果您想要更短的列表,则使用git log --oneline commitE
)。如果您查看列表,您将看到基本提交是commitC
.
Now you know that the base commit is commitC
and the last commit is commitE
, you can rebase OtherBranch (from its previous "master" to the new "master") as follows:
现在您知道基本提交是commitC
,最后一次提交是commitE
,您可以重新设置 OtherBranch(从其先前的“主”到新的“主”),如下所示:
# Replace the old OtherBranch with "master" and switch to it.
git checkout -B OtherBranch master
# Cherry-pick commits starting from commitC and ending at commitE.
cherry-pick commitC^..commitE
Alternatively (if you want to successfully complete the "rebase" before replacing OtherBranch
):
或者(如果您想在替换之前成功完成“rebase” OtherBranch
):
# Create new branch NewOtherBranch based off "master" and switch to it.
git checkout -b NewOtherBranch master
# Cherry-pick commits starting from commitC and ending at commitE.
cherry-pick commitC^..commitE
# Replace the old branch with the current branch (-M = --move --force)
git branch -M 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
).
在 git 中重新分支分支需要将当前分支切换到要更新的分支 ( OtherBranch
)。
With the git rebase
workflow, the following happens:
通过git rebase
工作流,会发生以下情况:
- Switch to
OtherBranch
(potentially branched off a very old base branch). - Rebase (internal step 1): Save commits that are not in the upstream branch.
- Rebase (internal step 2): Reset current branch to the (new) base branch.
- Rebase (internal step 3): Restore commits from step 2.
- 切换到
OtherBranch
(可能从一个非常旧的基础分支分支出来)。 - Rebase(内部步骤 1):保存不在上游分支中的提交。
- Rebase(内部步骤 2):将当前分支重置为(新)基础分支。
- Rebase(内部步骤 3):从步骤 2 恢复提交。
Step 1 and step 3 touch many files, but ultimately many of the touched files have not actually changed.
第1步和第3步接触了很多文件,但最终接触到的许多文件实际上并没有改变。
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:
我的方法将步骤1和3合并到步骤3中,因此触摸文件的数量最少。唯一涉及的文件是:
- Files that were changed between the base branch and the current commit in the current working tree.
- Files that are changed by the commits in the
OtherBranch
.
- 在当前工作树中的基本分支和当前提交之间更改的文件。
- 中的提交更改的文件
OtherBranch
。
回答by Snowbear
I would also love it, but this leaves no hope to me:
我也很喜欢它,但这对我来说没有希望:
If
<branch>
is specified, git rebase will perform an automatic git checkout before doing anything else. Otherwise it remains on the current branch.
如果
<branch>
指定, git rebase 将在执行任何其他操作之前执行自动 git checkout。否则它保留在当前分支上。
回答by iveqy
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.
因此,您想在结帐该分支之前为该分支完成 rebase 吗?我真的看不出这是什么原因,因为如果你不签出那个分支,你就无法处理它。为什么要重新定义一个你不工作的分支?进行结帐,它会更改您的 mtime,然后进行 rebase。变基将涉及更改的文件,当然您需要重建它们。
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.
但是,解决此问题的一种简单方法是使用其他工作树进行变基。只需将环境变量 GIT_WORK_TREE 设置为其他工作树。只是不要忘记让您的 HEAD 与您的工作树相匹配。
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:
根据他所在的分支和推送的内容,推送到非裸仓库可能很危险。更好的解决方案是使用宝贵的工作树从 repo 中获取。例子:
` orgbranch=$(git rev-parse HEAD)
` orgbranch=$(git rev-parse HEAD)
mkdir /tmp/tmp_wd
mkdir /tmp/tmp_wd
cp -r !(.git) /tmp/tmp_wd
cp -r !(.git) /tmp/tmp_wd
export GIT_WORK_TREE=/tmp/tmp_wd
导出 GIT_WORK_TREE=/tmp/tmp_wd
git checkout branch1
git 结帐分支 1
git rebase master
git rebase 大师
git checkout $orgbranch
git 结帐 $orgbranch
export GIT_WORK_TREE=
导出 GIT_WORK_TREE=
rm -rf /tmp/tmp_wd`
rm -rf /tmp/tmp_wd`