Insert a NON-EMPTY commit before the root commit in Git?

落爺英雄遲暮 提交于 2021-01-29 08:59:59

问题


I have a problem, please be kind enough to advise! I have an existing git repo, and for various reasons(that I wont go into here), I am trying to create a ROOT commit

Say this is my git commit history:

(ROOT) C1 <-C2 <-C3 <-C4 <-C5   <--branchname (HEAD)

I want to add an initial commit (CX, which is not empty) BEFORE C1. So it should end up like this:

(NEW ROOT) CX-C1 <-C2 <-C3 <-C4 <-C5   <--branchname (HEAD)

I found a similar question here: Insert a commit before the root commit in Git? But it's for appending an EMPTY git commit before the existing root. I also tried the steps in the answer here: https://stackoverflow.com/a/9736098/11491070 with one change: I replaced git commit --allow-empty -m 'initial' with git add .; git commit -m "initial laravel commit"; git push; and then this rebase step: git rebase --onto newroot --root master is failing with a TON of merge conflicts:

First, rewinding head to replay your work on top of it...
Applying: add initial quickadminpanel, with admin intrface and APIs for tags. Also added (but not yet enabled) ajax datatables module
Using index info to reconstruct a base tree...
.git/rebase-apply/patch:4537: trailing whitespace.
     * 
.git/rebase-apply/patch:4539: trailing whitespace.
     * 
.git/rebase-apply/patch:4547: trailing whitespace.
     * 
warning: 3 lines add whitespace errors.
Falling back to patching base and 3-way merge...
CONFLICT (add/add): Merge conflict in webpack.mix.js
Auto-merging webpack.mix.js
CONFLICT (add/add): Merge conflict in routes/web.php
Auto-merging routes/web.php
CONFLICT (add/add): Merge conflict in routes/api.php
Auto-merging routes/api.php
CONFLICT (add/add): Merge conflict in resources/views/welcome.blade.php
Auto-merging resources/views/welcome.blade.php
CONFLICT (add/add): Merge conflict in resources/sass/app.scss
Auto-merging resources/sass/app.scss
CONFLICT (add/add): Merge conflict in resources/sass/_variables.scss
Auto-merging resources/sass/_variables.scss
CONFLICT (add/add): Merge conflict in resources/lang/en/validation.php
Auto-merging resources/lang/en/validation.php
CONFLICT (add/add): Merge conflict in resources/lang/en/passwords.php
Auto-merging resources/lang/en/passwords.php
CONFLICT (add/add): Merge conflict in resources/lang/en/pagination.php
Auto-merging resources/lang/en/pagination.php
CONFLICT (add/add): Merge conflict in resources/lang/en/auth.php
Auto-merging resources/lang/en/auth.php
CONFLICT (add/add): Merge conflict in resources/js/bootstrap.js
Auto-merging resources/js/bootstrap.js
CONFLICT (add/add): Merge conflict in resources/js/app.js
Auto-merging resources/js/app.js
CONFLICT (add/add): Merge conflict in package.json
Auto-merging package.json
CONFLICT (add/add): Merge conflict in database/seeds/DatabaseSeeder.php
Auto-merging database/seeds/DatabaseSeeder.php
CONFLICT (add/add): Merge conflict in database/migrations/2014_10_12_100000_create_password_resets_table.php
Auto-merging database/migrations/2014_10_12_100000_create_password_resets_table.php
CONFLICT (add/add): Merge conflict in database/factories/UserFactory.php
Auto-merging database/factories/UserFactory.php
CONFLICT (add/add): Merge conflict in database/.gitignore
Auto-merging database/.gitignore
CONFLICT (add/add): Merge conflict in config/services.php
Auto-merging config/services.php
CONFLICT (add/add): Merge conflict in config/logging.php
Auto-merging config/logging.php
CONFLICT (add/add): Merge conflict in config/database.php
Auto-merging config/database.php
CONFLICT (add/add): Merge conflict in config/cache.php
Auto-merging config/cache.php
CONFLICT (add/add): Merge conflict in config/broadcasting.php
Auto-merging config/broadcasting.php

How can I fix this problem? Please help!


回答1:


First, a side note: you've drawn your repository forwards. Git does them backwards. Put the root commit last, i.e., on the left, and the latest commit first, i.e., on the right:

C1 <-C2 <-C3 <-C4 <-C5   <--branchname

The branch name always holds the hash ID of the latest commit. That's how Git knows which one is the latest. Git uses C5 to find C4, because commit C5 itself, which has a unique hash ID, contains the unique hash ID of commit C4. Meanwhile C4 holds the hash ID of C3, and so on; C2 holds the unique hash ID of C1, and C1 is a root commit because it has no parent hash ID inside it.

Second, no commit can actually be changed. So what you will do here is not "change commit C1"—neither you nor Git can do this—but rather, make a new commit, C1' or C21 or whatever we want to call it. Before this new commit we want another new commit, CX, that is a root commit and contains the desired content.

You can use the method in Antony Hatchkins's answer, but these days, the way to do that would be to start with git checkout --orphan (rather than tricky symbolic ref commands):

git checkout --orphan newroot
git rm -rf --cached .            # same as before
<arrange work tree as desired>   # same as before
git add .                        # or git add each file, same as before
git commit                       # same as before

That's the sequence of operations that creates commit CX, so that we have:

C1--C2--C3--C4--C5   <-- master

CX   <-- newroot

Now we get to the most interesting part. Using git rebase is the wrong way to go because rebase works by cherry-picking. This turns commit C1 into a request to add every file—commit C1, being a root commit, gets compared to the empty tree to see what changed—and as you've seen, this results in an add/add conflict for every file you added in C1 that is already in CX.

The question you must answer here—I cannot answer it for you—is: What content do you want in your new copy of C1 that is like C1 except that it has CX as its parent? That is, in the end, we'll have:

C1--C2--C3--C4--C5    [abandoned]

CX--C1'-C2'-C3'-C4-C5'   <-- master

in our repository. If I run git checkout on the hash ID of C1', should I see the same content as if I run git checkout on the hash ID of C1? The two commits will have different hash IDs, but the same author and committer and timestamp and log messages and so on. If they should have the same tree (snapshot) too, the job is now pretty easy, using git replace and git filter-branch. Here is the basic recipe:

git replace --graft <hash-of-C1> newroot

(and at this point you can run git log to make sure it looks right). Then run:

git filter-branch -- master

(assuming you want only the commits reachable from master copied, and ref master updated). The no-op filter-branch (no filters specified) copies each reachable commit, in the right (i.e., forwards, which is backwards to Git) order, copying C1 first, then C2, and so on, but—crucially—obeying the replacement directive. So now we have this:

C1--C2--C3--C4--C5    refs/original/refs/heads/master

CX   <-- refs/replace/<hash-of-C1>
  \
   C1'-C2'-C3'-C4-C5'   <-- master

If this is the desired final result, we now need only remove the refs/original/ name, and the no-longer-useful refs/replace/<hash-of-C1> name:

git update-ref -d refs/original/refs/heads/master
git update-ref -d refs/replace/<hash-of-C1>

Your history is now rewritten. All old clones must be destroyed (or at least, you should stop using them) because the hash IDs in them are those of the original commits, that you don't want to come back into your life to make that life miserable. The abandoned C1-through-C5 in your updated clone are harmless: they can be left there, in their abandoned state, until Git's Grim Reaper Collector, git gc, gets around to garbage-collecting them.



来源:https://stackoverflow.com/questions/59548124/insert-a-non-empty-commit-before-the-root-commit-in-git

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!