我可以在不修改工作副本的情况下对Git分支进行变基吗?

htzpubme  于 2023-04-28  发布在  Git
关注(0)|答案(8)|浏览(118)

假设我已经检查了我的“主”分支。我已经向“master”提交了一些生产更改,现在我想将我的“experimental”分支重定向到最新的master上。但是,我想这样做,而不修改我的工作副本中的任何文件。基本上,我希望所有的魔法都发生在里面。git目录,而不接触工作副本。
如果没有“不要修改我的工作副本”的要求,这将只是一个做的问题:

# current branch is master
git checkout experimental
git rebase master
git checkout master

我真实的的问题是,这会修改我工作副本中的时间戳,即使我结束时 checkout 的内容与开始时完全相同。只要我运行“git checkout experimental”,任何包含experimental分支中更改的文件都会将其mtime设置为当前时间--自从我上次重基experimental以来,所有在master中更改的文件也会如此。因为mtimes已经改变了,所以像构建工具这样的东西会认为它们需要再次做一些工作,即使在我完成这些工作的时候,文件的内容实际上并没有改变。(在我的例子中,如果项目文件的时间戳发生了变化,Visual Studio认为它需要花费大量的时间来卸载和重新加载项目。我想避免这种情况。

有没有一种方法可以在一个步骤中完成上述所有操作,而无需修改工作副本中的任何内容*(假设在变基过程中没有冲突)*

(If如果有冲突,我的首选是显示错误,然后中止整个操作,而不修改任何时间戳。但这只是我的偏好,不是一个硬性要求-我不知道什么都是可能的。)
当然我可以写一个脚本来捕获mtimes,运行git,然后重置mtimes;但是看起来Git已经有办法在不影响工作副本的情况下做诸如rebase之类的事情了,因为rebase实际上是关于增量的,而不是文件的实际内容。

nkcskrwz

nkcskrwz1#

由于git 2.5,更好的解决方案是使用第二个worktree
一个git仓库可以支持多个工作树,允许你一次 checkout 多个分支。

$ git worktree add ../second-copy experimental
$ cd ../second-copy/
$ git rebase master experimental

就是这样之后,如果需要,可以使用rm -rf second-copy,或者保留它以备将来进行更多的重基。

$ git rebase master experimental
hc2pp10m

hc2pp10m2#

有没有一种方法可以在一个步骤中完成上述所有工作,而无需修改工作副本中的任何内容?
不幸的是,这是不可能的(不创建工作副本的可修改副本-see also Petr's answer),因为git在工作树上执行所有的合并操作(真实的的合并,cherry-pick,rebase,patch application)。这一点在前面已经提到过好几次,例如one of the knowledgeable Jakub Narębski's answers
合并(或重基)不接触工作目录(和索引)是不可能的,因为可能存在必须使用工作目录(和/或索引)来解决的合并冲突。
是的,这是一个设计决策,但这是一个非常可以理解的决策--在内存中构建所有必要的结构来尝试合并,然后一旦遇到冲突,就把所有东西都转储到工作树中,这有点麻烦,而你可以简单地在工作树中首先完成它。(我不是Git开发者;别把这当成绝对的真理。可能还有其他原因。)
我的建议是,与其编写一个脚本来完成所有的mtime操作,不如简单地克隆仓库,在克隆中执行rebase,然后将其推回原始仓库:

git clone project project-for-rebase
cd project-for-rebase
git branch experimental origin/experimental
git rebase master experimental
git push origin experimental

当然,这是假设experimental没有在您的原始存储库中检出。如果是这样的话,你就不用推了,你可以做一些像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。这自然会触及原始实验分支和重定基实验分支之间的任何不同文件,但这绝对是正确的行为。(当然,这不是你给出的例子,但我希望这些说明是一般的!)

t30tvxxf

t30tvxxf3#

我已经创建了一个小脚本来在linux上完成这个任务。它是基于Jefromi的答案和一些补充(主要是,设置备用对象,这样对象数据库就不会被复制,并且只提取需要的分支)。有些人可能会发现它很有用:https://github.com/encukou/bin/blob/master/oot-rebase
如果它不做你喜欢的事情,欢迎pull requests:)

rqcrx0a6

rqcrx0a64#

更新,自git 2。这个答案被基本上相同的内置机制“工作树”所取代。见上面的答案:https://stackoverflow.com/a/12481546/1499102
类似于创建存储库的克隆,我发现使用多个workdirer来完成类似的事情要整洁得多。克隆也将使用大量的磁盘空间,这将使用几乎没有。
https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir
创建一个新的workdir,如下所示:

git-new-workdir project-dir new-workdir branch

然后你可以把它当作一个克隆,除了在你原来的workdir中的fetch和commit会在这里被反映出来(尽管在没有recheckout的情况下不会在工作分支上)。唯一的例外是如果你有子模块,因为默认情况下这些子模块是为每个工作目录单独完成的。坦率地说,我从来没有看过,因为我试图避免子模块。
基本上就是:

cd new-workdir
git checkout experimental
git rebase master

不完全是一个单一的命令,但相当简单。
与下面的stash方法相比,这种方法(类似于clone方法)的一个优点是,如果您的工作目录中的代码当前正在执行(或被某些进程使用),它不会被中断。
这里没有提到的另一个选择是在当前工作目录中执行此操作,但将更改隐藏起来,以便可以立即恢复工作目录状态。

# current branch is master (with changes to working state)
git stash -u
git checkout experimental
git rebase master
git checkout master
git stash pop

如果您有任何新文件,请确保使用stash -u,否则它们将不会被隐藏。同样,不是一个步骤,但相当干净和简单。

iklwldmw

iklwldmw5#

正如其他人所说的,不可能在不触及工作目录的情况下对分支进行变基(即使是建议的替代方案,如创建新的克隆或工作树,也无法改变这一事实;这些替代方案确实不会触及您当前的工作目录,而只是通过创建一个新的工作树)。
对于您想要更新的分支将基于当前工作树(或其父树)进行重基的特殊情况,可以在不必要地触及文件的情况下“重基”另一个分支。
如果你有一个git工作流,你在许多分支上工作,这些分支都是从主分支“master”分支(定期更新到远程master分支)分支出来的,这种特殊情况经常发生。
为了说明,假设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

为了示例起见,让我们假设“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

...然后你摆弄当前的分支,做一些耗时的编译。最终,你决定要在OtherBranch上工作。这个OtherBranch应该重新基于master(最好使用最少的文件系统操作)。下一节将说明如何操作。

重定其他分支的基(参考示例-不要这样做)

下面的解决方案是用git的方式来实现的:

git checkout OtherBranch
git rebase master    # or git rebase origin/master

这样做的缺点是,第一个命令会更改当前工作树的日期,即使第二个命令将恢复文件。

以最小的更改重新构建其他分支

为了最小化被触及的文件的数量,你需要 checkout 到新的基础分支,然后使用git cherry-pickOtherBranch中的所有额外提交应用到基础分支之上。
在执行任何操作之前,您需要识别OtherBranch中的提交。

  • git log OtherBranch显示了OtherBranch上的提交(如果您还没有更改OtherBranch,这很有用)
  • git reflog显示本地仓库中分支的更改(如果您已经更新了分支并犯了错误,则会很有用)。

在当前示例中,您将发现OtherBranch上的最后一次提交是commitE。你可以看到git log commitE之前提交的列表(或者如果你想要更短的列表,git log --oneline commitE)。如果你浏览这个列表,你会看到基本提交是commitC
现在你知道基提交是commitC,最后一次提交是commitE,你可以重新设置OtherBranch(从以前的“master”到新的“master”),如下所示:

# 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

或者(如果您想在替换OtherBranch之前成功完成“rebase”):

# 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

为什么会这样?

在git中重基分支需要将当前分支切换到你想要更新的分支(OtherBranch)。
使用git rebase工作流,会发生以下情况:
1.切换到OtherBranch(可能从一个非常旧的基本分支分支分支出来)。
1.重定基(内部步骤1):保存不在上游分支中的提交。
1.重定基(内部步骤2):将当前分支重置为(新)基本分支。
1.重定基(内部步骤3):从步骤2恢复提交。
第1步和第3步涉及许多文件,但最终许多被触及的文件实际上没有更改。
我的方法将步骤1和步骤3合并到步骤3中,因此所接触的文件的数量是最小的。唯一被触及的文件是:

  • 在基分支和当前工作树中的当前提交之间更改的文件。
  • OtherBranch中被提交更改的文件。
c2e8gylq

c2e8gylq6#

我也很喜欢,但这对我来说没有希望:
如果指定了<branch>,git rebase会在执行其他操作之前执行自动git checkout。否则,它将保留在当前分支上。
http://git-scm.com/docs/git-rebase

abithluo

abithluo7#

Craig- P. Motlinanswer建议使用**worktree**。
自Git 2以来,这一点更加强大。41(2023年第2季度)确保已教授一些子命令,以阻止用户在链接到同一存储库的另一个工作树中使用的分支上工作。

rebase:拒绝转到已在别处 checkout 的分支

签字人:鲁本·胡斯托
b5cabb4(“rebase:拒绝切换到已经在其他地方 checkout 的分支”,2020-02-23,Git v2。26.0-rc 0--merge),我们添加了一个条件,以防止涉及切换到已在另一个工作树中检出的分支的重基操作。
最近修复了一个错误,导致它无法按预期工作。
让我们添加一个测试来注意将来是否会发生变化。

6bc51xsx

6bc51xsx8#

所以你想在 checkout 一个分支之前为它做一个变基操作?我真的看不出其中的原因,因为如果你不 checkout 那个分支你就不能在它上工作。你为什么要为一个你不工作的分支重定基?做结帐,它会改变你的mtime,然后做rebase。rebase将触及被更改的文件,当然你需要重建它们。
但是,解决这个问题的一个简单方法是使用其他工作树进行变基。只需将环境变量GIT_WORK_TREE设置为其他工作树。只是不要忘记让你的HEAD匹配你的工作树。
根据他所在的分支以及推的是什么,推到非裸回购可能是危险的。一个更好的解决方案是从仓库中获取宝贵的工作树。示例:
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

相关问题