TL;DR;
In your original repo clone, you should:
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.
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. 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:
- Clone original repository
- Add a remote to the bad repo
- Fetch the bad repo
master
branch
- Branch into the fetched bad repo
- Rebase the bad master branch to your master (will claim some changes are already applied)
- Merge this branch into your master
- Push back to original repository
- 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.
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
. If you really want to throw those commits away, i.e. 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.
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:
- Create original good repo history:
A <- B <- C
- Manually copied original contents to messed up repo
- Generate messed up commit history:
M <- N <- O <- P
, where M
has the same content as original A <- B <- C
- Add work to original repo:
... C <- D <- E