问题
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