git 命令使一个分支像另一个分支一样

发布于 2024-10-16 05:34:31 字数 347 浏览 6 评论 0原文

我正在尝试对一个进行更改的分支并将其恢复到与它所分歧的上游相同。这些更改都是本地的,并且已推送到 github,因此 git resetgit rebase 都不是真正可行的,因为它们更改了历史记录,这对于分支来说是一件坏事那已经被推动了。

我还尝试过使用各种策略进行 git merge ,但它们都没有撤消本地更改,即如果我添加了一个文件,合并可能会使其他文件恢复一致,但我仍然会拥有上游没有的文件。

我可以在上游创建一个新分支,但我真的想要一个合并,就修订历史记录而言,应用所有更改来获取我的分支并使其再次与上游相同,以便我可以安全地推送该更改没有破坏历史。有这样的命令或一系列命令吗?

I'm trying to take a branch with changes and bring it back to be identical to the upstream it diverged from. The changes are both local and have been pushed to github, so neither git reset or git rebase are really viable, since they change history, which is a bad thing with a branch that's already been pushed.

I've also tried git merge with various strategies but none of them undo the local changes, i.e. if I'd added a file, a merge might bring other files back in line, but I'll still have that file that the upstream doesn't have.

I could just create a new branch off the upstream, but i'd really like a merge that in terms of revision history applies all the changes to take my branch and make it identical to the upstream again, so that I can safely push that change without clobbering history. Is there such a command or series of commands?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

起风了 2024-10-23 05:34:31

您可以使用自定义合并驱动程序“keepTheirs”将上游分支合并到dev分支:
请参阅“git merge -s 他们的”需要 - 但我知道它不存在”。
在您的情况下,只需要一个 .gitattributes 和一个 keepTheirs 脚本,例如:

mv -f $3 $2
exit 0

git merge --strategy=theirs 模拟 #1

< strong>显示为合并,上游作为第一个父级。

Jefromi 提到(在评论中) merge -s ours,通过合并上游(或从上游开始的临时分支)上的工作,然后将分支快进到合并的结果:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

这样做的好处是将上游祖先记录为第一个父级,因此合并意味着“吸收这个过时的主题分支”而不是“销毁这个主题分支并将其替换为上游”< /strong>.


2023 年更新:例如,如果您希望 main 准确反映 dev 是什么:

git switch -c tmp dev
git merge -s ours main   # ignoring all changes from main
git switch main
git merge tmp            # fast-forward to tmp HEAD, which is dev
git branch -D tmp        # deleting tmp

(2011 年编辑):

此工作流程已在此 OP 的博客文章:

为什么我又想要这个?

只要我的存储库与公共版本无关,这一切都很好,但从现在起我希望能够与其他团队成员和外部贡献者在 WIP 上进行协作,我想确保我的公共分支对于其他人来说是可靠的,可以从中分支和拉取,即不再对我推送到远程备份的内容进行变基和重置,因为它现在位于 GitHub 上并公开。

所以这让我知道我应该如何继续。
99% 的情况下,我的副本将进入上游主控,因此我想在大部分时间工作我的主控并推送到上游。
但每隔一段时间,我在 wip 中的内容就会因进入上游的内容而失效,我会放弃 wip 的某些部分。
那时,我想让我的主控恢复与上游同步,但不破坏我公开推送的主控上的任何提交点。即我想要与上游合并,最终得到使我的副本与上游相同的变更集
这就是 git merge --strategy=theirs 应该做的。


git merge --strategy=theirs 模拟 #2

显示为合并,我们的作为第一个父级。

(由 jcwenger)

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp, but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs 模拟#3

这个博客文章提及

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

有时您确实想这样做,并不是因为您的历史记录中有“垃圾”,而是也许是因为您想更改公共存储库中的开发基线,在公共存储库中应该避免变基 .


git merge --strategy=theirs 模拟#4

(同一篇博文)

或者,如果您想保持本地上游分支可快速转发,一个潜在的折衷方案是理解对于 sid/unstable,上游分支可以不时地重置/重新基址(基于以下事件)最终超出了上游项目方面您的控制范围)。
这不是什么大问题,并且使用该假设意味着可以轻松地将本地上游分支保持在仅进行快进更新的状态。

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs 模拟 #5

(由 提出) Barak A. Pearlmutter):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs 模拟#6

(由同一Michael Gebetsroither):

Michael Gebetsroither 插话,声称我在“作弊”;)并给出了另一个具有较低级别管道命令的解决方案:

(如果仅使用 git 命令无法实现,那么它就不是 git,git 中带有 diff/patch/apply 的所有内容都不是真正的解决方案;)。

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g. superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs 模拟#7

必要的步骤可以描述为:

  1. 用上游替换您的工作树
  2. 将更改应用到索引
  3. 添加上游作为第二个父级
  4. 提交

命令git read-tree用不同的树覆盖索引,完成第二步,并具有更新工作树的标志,完成第一步。提交时,git 使用 .git/MERGE_HEAD 中的 SHA1 作为第二个父级,因此我们可以填充它来创建合并提交。因此,这可以通过以下方式完成:


git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit

Git 2.44(2024 年第 1 季度)还提出了一种自定义合并驱动程序方法(我在“git merge -s 他们的 需要,但它不存在”)。

自定义合并驱动程序需要访问它们正在处理的修订的名称,以便它们引入的合并冲突标记可以引用这些修订。
用于共同祖先、本地头和其他头的冲突标签可以通过使用占位符“%S”、“%X”和“%Y' 分别。

You could merge your upstream branch to your dev branch, with a custom merge driver, "keepTheirs":
See "git merge -s theirs” needed — but I know it doesn't exist".
In your case, only one .gitattributes would be required, and a keepTheirs script like:

mv -f $3 $2
exit 0

git merge --strategy=theirs Simulation #1

Shows as a merge, with upstream as the first parent.

Jefromi mentions (in the comments) the merge -s ours, by merging your work on the upstream (or on a temp branch starting from upstream), and then fast-forwarding your branch to the result of that merge:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

This has the benefit of recording the upstream ancestor as the first parent, so that the merge means "absorb this out-of-date topic branch" rather than "destroy this topic branch and replace it with upstream".

Update 2023: for instance, if you want main to reflect exactly what dev is:

git switch -c tmp dev
git merge -s ours main   # ignoring all changes from main
git switch main
git merge tmp            # fast-forward to tmp HEAD, which is dev
git branch -D tmp        # deleting tmp

(Edit 2011):

This workflow has been reported in this blog post by the OP:

Why do I want this again?

As long as my repo had nothing to do with the public version, this was all fine, but since now I'd want the ability to collorate on WIP with other team members and outside contributors, I want to make sure that my public branches are reliable for others to branch off and pull from, i.e. no more rebase and reset on things I've pushed to the remote backup, since it's now on GitHub and public.

So that leaves me with how I should proceed.
99% of the time, my copy will go into the upstream master, so I want to work my master and push into upstream most of the time.
But every once in a while, what I have in wip will get invalidated by what goes into upstream, and I will abandon some part of my wip.
At that point, I want to bring my master back in sync with upstream, but not destroy any commit points on my publicly pushed master. I.e. I want a merge with upstream that ends up with the changeset that make my copy identical to upstream.
And that's what git merge --strategy=theirs should do.


git merge --strategy=theirs Simulation #2

Shows as a merge, with ours as the first parent.

(proposed by jcwenger)

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp, but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Simulation #3

This blog post mentions:

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

sometimes you do want to do this, and not because you have "crap" in your history, but perhaps because you want to change the baseline for development in a public repository where rebasing should be avoided.


git merge --strategy=theirs Simulation #4

(same blog post)

Alternatively, if you want to keep the local upstream branches fast-forwardable, a potential compromise is to work with the understanding that for sid/unstable, the upstream branch can from time to time be reset/rebased (based on events that are ultimately out of your control on the upstream project's side).
This isn't a big deal, and working with that assumption means that it's easy to keep the local upstream branch in a state where it only takes fast-forward updates.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Simulation #5

(proposed by Barak A. Pearlmutter):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Simulation #6

(proposed by the same Michael Gebetsroither):

Michael Gebetsroither chimed in, claiming I was "cheating" ;) and gave another solution with lower-level plumbing commands:

(it wouldn't be git if it wouldn't be possible with git only commands, everything in git with diff/patch/apply isn't a real solution ;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g. superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Simulation #7

The necessary steps can be described as:

  1. Replace your worktree with upstream
  2. Apply the changes to the index
  3. Add upstream as the second parent
  4. Commit

The command git read-tree overwrites the index with a different tree, accomplishing the second step, and has flags to update the work tree, accomplishing the first step. When committing, git uses the SHA1 in .git/MERGE_HEAD as the second parent, so we can populate this to create a merge commit. Therefore, this can be accomplished with:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit

Git 2.44 (Q1 2024) also proposes a custom merge driver approach (that I illustrate in "git merge -s theirs needed, but it does not exist").

Custom merge drivers need access to the names of the revisions they are working on, so that the merge conflict markers they introduce can refer to those revisions.
The conflict labels to be used for the common ancestor, local head and other head can be passed by using the placeholders '%S', '%X' and '%Y' respectively.

烟花肆意 2024-10-23 05:34:31

在我看来,您只需要这样做:

$ git reset --hard origin/master

如果没有任何更改可以推送到上游,并且您只是希望上游分支成为您当前的分支,那么就可以做到这一点。在本地执行此操作没有什么害处但是您将丢失任何尚未推送到 master 的本地更改**。

** 实际上,如果您在本地提交了更改,这些更改仍然存在,因为提交仍将保留在您的 git reflog 中,通常至少保留 30 天。

It sounds to me like you just need to do:

$ git reset --hard origin/master

If there is no change to push upstream, and you simply want the upstream branch to be your current branch, this will do that. It is not harmful to do this locally but you will lose any local changes** that haven't been pushed to master.

** Actually the changes are still around if you have committed them locally, as the commits will still be in your git reflog, usually for at least 30 days.

疯到世界奔溃 2024-10-23 05:34:31

您现在可以相当轻松地做到这一点:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

这使您的本地存储库与源同步,并保留历史记录。

You can do this rather easily now:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

This gets your local repo in-sync with the origin, and preserves the history.

╄→承喏 2024-10-23 05:34:31

git merge -s theirs ref-to-be-merged 的​​另一个模拟:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

双重重置的替代方法是应用反向补丁:

git diff --binary ref-to-be-merged | git apply -R --index

Another simulation for git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

An alternative to the double reset would be applying the reverse patch:

git diff --binary ref-to-be-merged | git apply -R --index
爱你是孤单的心事 2024-10-23 05:34:31

还有一种几乎不需要管道命令帮助的方法 - 恕我直言,这是最直接的方法。假设您想模拟 2 个分支的“他们的”情况:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

这使用其中一个分支的树(上例中的栏,提供“他们的”树)合并任意数量的头(上例中为 2 个),忽略任何差异/文件问题(commit-tree 是低级命令,因此它不关心这些)。请注意,head 可以仅为 1(因此相当于用“theirs”进行樱桃挑选)。

请注意,首先指定哪个父头可能会影响某些内容(请参见例如 git-log 命令的 --first-parent ) - 因此请记住这一点。

除了 git-show 之外,还可以使用任何其他能够输出树和提交哈希的东西 - 无论哪个用于解析(cat-file、rev-list,...)。您可以使用 git commit --amend 来跟踪所有内容,以交互方式美化提交消息。

There's also a way with little help of plumbing command - IMHO the most straightforward. Say you want to emulate "theirs" for 2 branches case:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

This merges arbitrary number of heads (2 in the example above) using tree of one of them (bar in the example above, providing 'theirs' tree), disregarding any diff/file issues (commit-tree is low level command, so it doesn't care about those). Note that head can be just 1 (so equivalent of cherry-pick with "theirs").

Note, that which parent head is specified first, can influence some stuff (see e.g. --first-parent of git-log command) - so keep that in mind.

Instead of git-show, anything else capable of outputting tree and commit hashes can be used - whatever one's is used to parsing (cat-file, rev-list, ...). You can follow everything with git commit --amend to interactively beautify commit message.

山人契 2024-10-23 05:34:31

虽然很严厉,但是见鬼,可能会出什么问题呢?

  • 查看您希望与 Y 类似的分支 X
  • cp -r .git /tmp
  • 查看分支 Y git checkout y
  • rm -rf .git & & cp -r /tmp/.git
  • 提交并提交推动任何差异
  • 完成。

Heavy handed, but hell, what can possibly go wrong?

  • Check out the branch X you want to look like the Y
  • cp -r .git /tmp
  • Check out branch Y git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Commit & push any difference
  • DONE.
梦情居士 2024-10-23 05:34:31

更改为远程上游分支并执行 git merge,并将合并策略设置为 ours

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

所有历史记录仍然存在,但您将有一个额外的合并提交。这里重要的是从您想要的版本开始,并将我们的版本与 github 实际所在的分支合并。

change to the remote upstream branch and do a git merge with the merge strategy set to ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

All the history will still be present, but you'll have an extra merge commit. The important thing here is to start from the version you want to be at and merge ours with the branch github is actually at.

顾挽 2024-10-23 05:34:31

向后使用 git reset !

您可以使用 git reset 使分支看起来像任何其他提交,但您必须以迂回的方式进行。

要使提交 上的分支看起来像提交 ,您可以执行

git reset --hard <new>

以下操作: > 工作树的内容。

然后将

git reset --mixed <old> 

分支更改回原始提交但将工作树保留在状态

然后您可以添加并提交更改,以使您的分支与 提交的内容完全匹配。

这是违反直觉的,要从 状态转移到 ,您需要执行 git Reset <新> <旧>。然而,使用选项--mixed,工作树保留在,分支指针设置为,所以当提交更改时,分支看起来就像我们想要的那样。

警告

不要忘记您的提交,例如在执行 git reset --hard 时忘记 是什么。

Use git reset BACKWARDS!

You can make a branch look like any other commit with git reset, but you have to do it in a round-about way.

To make a branch on commit <old> look like a commit <new>, you can do

git reset --hard <new>

in order to make <new> the contents of the working tree.

Then do

git reset --mixed <old> 

to change the branch back to the original commit but leaving working tree in the <new> state.

Then you can add and commit the changes, in order to make your branch exactly match the contents of the <new> commit.

It's counter-intuitive that to move from the <old> state to the <new> you need to do a git reset from <new> to <old>. However with the option --mixed the working tree is left at <new> and the branch pointer set to <old>, so that when the changes are committed the branch looks how we want.

Warning

Don't lose track of your commits, e.g. forget what <old> is when doing git reset --hard <new>.

合约呢 2024-10-23 05:34:31

我遵循这些角色:

从分支获取、硬重置,然后从他们的分支递归,然后强制推送到分支

自行承担风险

git fetch
git reset --hard <branch>
git merge <branch> -s recursive -X theirs
git push -f <remote> <branch>

I followed those roles:

Fetching, reset hard from the branch then recursive from theirs and then forced push to the branch

ON YOUR OWN RISK

git fetch
git reset --hard <branch>
git merge <branch> -s recursive -X theirs
git push -f <remote> <branch>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文