简体   繁体   English

从复制的fork存储库重新应用Git提交到原始存储库

[英]Reapply Git commits from copied fork repository to original repository

A university colleague of mine thought it was a good idea to fork a repository by cloning it and copy its contents into a new, freshly initialized repository but without the .git folder from the original repository. 我的一位大学同事认为通过克隆存储库并将其内容复制到新的初始化存储库但没有原始存储库中的.git文件夹来分叉存储库是个好主意。 Afterwards, he simply committed this copy using a single commit and the whole team began working on the project based on this commit: 之后,他只使用一次提交就提交了此副本,整个团队就开始根据此提交开发项目:

A <- B <- C     <- D <- E    (original repository)
\  clone  /        |_____| 
 \       /            |
  \     /     Ofc. work on the original repository was continued after cloning...
   \   /
     M <- N <- O <-P    (our "fork", commits from my team)

Now, my first goal is to get the following repository structure: 现在,我的第一个目标是获得以下存储库结构:

A <- B <- C <- N <- O <- P

What I have been trying to do now during the past few hours is the following: 我在过去几个小时里一直试图做的事情如下:

    • Clone the original repository. 克隆原始存储库。
    • git diff > /path/to/patch from within the fork. 从fork中获取git diff > /path/to/patch
    • git apply within the original repository. git apply在原始存储库中应用。
    • Works, but does not preserve the commits. 工作,但不保留提交。
  1. Various other things that will not work. 各种其他不起作用的东西。
    • Clone the original repository. 克隆原始存储库。
    • Create and switch to a new branch. 创建并切换到新分支。
    • Reset it to the commit A using git reset --hard COMMIT_HASH_A . 使用git reset --hard COMMIT_HASH_A将其重置为提交A
    • Create a patch from N <- O <- P using git format-patch COMMIT_HASH_M --stdout > /path/to/patch on the fork. 使用git format-patch COMMIT_HASH_M --stdout > /path/to/patch在fork上创建一个N <- O <- P git format-patch COMMIT_HASH_M --stdout > /path/to/patch
    • Apply this patch on the original repository using git am -3 /path/to/patch . 使用git am -3 /path/to/patch在原始存储库上应用此修补git am -3 /path/to/patch After resolving several conflicts such as the duplicate creation of empty files, this will result in the following error: fatal: sha1 information is lacking or useless (some_file_name). Repository lacks necessary blobs to fall back on 3-way merge. 在解决了几个冲突(例如重复创建空文件)之后,这将导致以下错误: fatal: sha1 information is lacking or useless (some_file_name). Repository lacks necessary blobs to fall back on 3-way merge. fatal: sha1 information is lacking or useless (some_file_name). Repository lacks necessary blobs to fall back on 3-way merge. This is where I cannot get on. 这是我无法上场的地方。

So how do I create a repository including all commits from the original repository and from our team as described, so that eventually, a pull request could be sent to the original repository? 那么如何创建一个存储库,包括原始存储库和我们团队的所有提交,如上所述,最终拉取请求可以发送到原始存储库? Might a git-rebase help? git-rebase帮助吗?

If you don't insist on linear history, you can merge your fork into original repository. 如果您不坚持线性历史记录,则可以将fork合并到原始存储库中。

In the original repo drirectory: 在原来的repo drirectory:

git remote add fork /path/to/fork
git fetch fork
git merge fork/master

This will preserve commits and may result in liner history (no merge commit) if the merge can be fast-forwarded. 如果可以快速转发合并,这将保留提交并可能导致班轮历史记录(无合并提交)。

TL;DR; TL; DR;

In your original repo clone, you should: 在您的原始repo克隆中,您应该:

git remote add colleague /path/to/colleague
git fetch colleague
git checkout -b colleague colleague/master
git rebase master
git checkout master
git merge colleague

This will give you linear history and will not leave behind a redundant and parent-less M commit. 这将为您提供线性历史记录,并且不会留下冗余和无父M提交。

This is different from David Siro's answer , which will produce a merge commit that also leaves a redundant/parent-less M commit floating around in the branch you merged from. 这与David Siro的答案不同,后者将产生一个合并提交,它还会在您合并的分支中留下冗余/无父M提交。 I don't like that dangling-commit scenario. 我不喜欢那种悬空提交方案。

Original Post 原帖

I replicated your good and bad repository histories and was able to solve the problem by basically rebasing a remote. 我复制了你的好的和坏的存储库历史,并且能够通过基本改变远程来解决问题。

These are the steps I followed: 这些是我遵循的步骤:

  1. Clone original repository 克隆原始存储库
  2. Add a remote to the bad repo 将一个遥控器添加到坏回购
  3. Fetch the bad repo master branch 获取错误的repo master分支
  4. Branch into the fetched bad repo 分成了糟糕的回购
  5. Rebase the bad master branch to your master (will claim some changes are already applied) 将坏主分支重新定位到主分支(将声明已应用某些更改)
  6. Merge this branch into your master 将此分支合并到您的主服务器中
  7. Push back to original repository 推回原始存储库
  8. Schedule you colleague's demise 😉 安排你同事的死亡😉

With that setup, the commands I used and key output follows. 使用该设置,我使用的命令和键输出如下。

#
# Step 1
#
$ git clone <path-to-original-repo>
$ cd original-repo

#
# Step 2
#
$ git remote add messed-up-repo <path-to-messed-up-repo>

#
# Step 3
#
$ git fetch messed-up-repo

#
# Step 4
#
$ git checkout -b bad-master bad-orig/master

#
# Step 5
#
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: commit M
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
Applying: commit N
Applying: commit O
Applying: commit P

#
# Step 5.1: look at your new history
#
$ git log --oneline --graph --decorate
* cc3121d (HEAD -> bad-master) commit P
* 1144414 commit O
* 7b3851c commit N
* b1dc670 (origin/master, origin/HEAD, master) commit E
* ec9eb4e commit D
* 9c2988f commit C
* 9d35ed6 commit B
* ae9fc2f commit A

#
# Step 6
#
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge bad-master 
Updating b1dc670..cc3121d
Fast-forward
 n.txt | 1 +
 o.txt | 1 +
 p.txt | 1 +
 3 files changed, 3 insertions(+)
 create mode 100644 n.txt
 create mode 100644 o.txt
 create mode 100644 p.txt

#
# Step 7
#
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 714 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To /tmp/repotest/good-orig.git
   b1dc670..cc3121d  master -> master

#
# Step 7.1: look at your history again
#
$ git log --oneline --graph --decorate
* cc3121d (HEAD -> master, origin/master, origin/HEAD, bad-master) commit P
* 1144414 commit O
* 7b3851c commit N
* b1dc670 commit E
* ec9eb4e commit D
* 9c2988f commit C
* 9d35ed6 commit B
* ae9fc2f commit A

You can now destroy your colleague's messed up repository with fire and get others to continue using the original, and now fixed, repository. 您现在可以使用fire来销毁同事的混乱存储库,并让其他人继续使用原始的,现在已修复的存储库。

Note: In your post, you said you wanted commits: 注意:在你的帖子中,你说你想要提交:

 A <- B <- C <- N <- O <- P 

But my solution includes commits D and E inbetween: A <- B <- C <- D <- E <- N <- O <- P . 但我的解决方案包括其间的提交DEA <- B <- C <- D <- E <- N <- O <- P If you really want to throw those commits away, ie assuming it's not a typo in your post , then you can simply git rebase -i HEAD~5 , remove the pick lines for those commits, and then git push --force to your good repo's origin. 如果你真的想抛出这些提交,即假设它不是你帖子中的拼写错误 ,那么你可以简单地git rebase -i HEAD~5 ,删除那些提交的pick行,然后git push --force to good回购的起源。

I'm assuming you understand the implications of re-writing history and that you need to communicate with your users so that they don't get bit by it. 我假设您了解重写历史的含义,并且您需要与您的用户进行沟通,以便他们不会对此感到厌烦。


For the sake of completeness, I replicated your setup as follows: 为了完整起见,我按如下方式复制了您的设置:

  1. Create original good repo history: A <- B <- C 创建原始的良好回购历史: A <- B <- C
  2. Manually copied original contents to messed up repo 手动复制原始内容以搞砸了回购
  3. Generate messed up commit history: M <- N <- O <- P , where M has the same content as original A <- B <- C 生成混乱的提交历史记录: M <- N <- O <- P ,其中M与原始A <- B <- C具有相同的内容
  4. Add work to original repo: ... C <- D <- E 将工作添加到原始仓库: ... C <- D <- E

First, a note: As with all repository-wide "rewrite everything" operatinos, do this on a clone . 首先,注意:与所有存储库范围内的“重写所有”操作一样, 在克隆上执行此操作。 If it goes well, great! 如果进展顺利,太棒了! If it fails horribly, remove the clone and you're no worse off than you were before. 如果它失败了,删除克隆,你不会比以前更糟糕。 :-) :-)

As jthill suggested in a comment , you can use grafts, or the more modern git replace , and then use git filter-branch to make the grafting permanent. 正如jthill在评论中建议的那样 ,你可以使用移植物,或更现代的git replace ,然后使用git filter-branch使移植永久化。 This assumes that the trees associated with the commits are correct, ie, that you do not want any changes made to the source associated with each commit (which is probably true). 这假设与提交关联的是正确的,即您不希望对与每个提交关联的源进行任何更改(这可能是真的)。 See How do git grafts and replace differ? 请参阅git移植和替换有何不同之处? (Are grafts now deprecated?) and How to prepend the past to a git repository? (现在是否已弃用移植?)以及如何将过去添加到git存储库? for more on using grafts and git replace . 有关使用移植物和git replace

Given that the two repositories started from a common base, you can also use the method outlined in David Siro's answer . 鉴于这两个存储库是从一个共同基础开始的,您也可以使用David Siro的答案中概述的方法。 There is one step missing: after git remote add you must run git fetch to mingle the two repositories into a new "union repository", as it were. 缺少一个步骤:在git remote add你必须运行git fetch将这两个存储库混合到一个新的“union存储库”中。 I think this method is actually simpler and easier, and would try it first. 我认为这种方法实际上更简单,更容易,并且会先尝试一下。 Once the two repositories have been combined into one, you can rebase, merge, filter-branch, etc., as you like. 将两个存储库合并为一个存储库后,您可以根据需要进行rebase,merge,filter-branch等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM