This question already has an answer here:
-
Insert a commit before the root commit in Git?
14 answers
New Question
Two questions:
I'm trying to put a commit before ALL commits.
The bottommost commit has 660fb2a76211f36ec9a67d0454a90eab469e9fd0
as SHA. When I type git rebase -i 660fb2a76211f36ec9a67d0454a90eab469e9fd0
every commit but the last one gets displayed in the list.
I really need this commit to appear so I can put the very first commit as very last!
When I put the very first commit as first in the list (meaning second commit in total, because the very first is not in the list as mentioned above) I get an error: error: could not apply b722c76... v1.4.3 BEAT release
I just cut it from the bottom of the list and put it to the top! I did not change the number!
I also tried this several times. The same result.
That's it so far. If you have questions go ahead and ask!
Original Question
I just dicovered old backups of my project. These backups where created before I used git.
I now would like to add them to my repository as old commits. That would mean I have to put these commit in front of all other commits.
Now there are several problems with this:
- How do I in general put a commit before other ones?
- How could I do this fast? (I have many backups!)
- How do I set a date for these "old" commits? (I know the dates of the backups!)
If something is not clear, please mention it. I will fix the issue then!
One last thing:
I have published this on GitHub. And I mainly used their software to commit commits. So how do I push this back to GitHub?
Update
I found another, slightly simpler way to do this, using the --root
flag of git rebase
. I tried it out with a repo of mine, so I know it works (at least for a completely linear history, more on that at the end).
Step 1: Create a backup clone of your repo
Let's play it safe and make a backup, just in case we end up doing something disastrous and losing your data:
git clone original-repo backup-repo
Step 2: Create orphan branch (this will be your new root)
Checkout the orphan branch. Specifying a start-point commit/branch is optional, if you leave that argument out, then Git will default to using your current commit as the start-point (it follows the pattern of standard branching syntax):
git checkout --orphan new-master firstCommitOfOldMaster
I specified the root of the old master as the start-point, because it will probably make the following step faster (removing the working directory files).
Step 3: Remove working directory files
Creating the orphan branch new-master
from your old master might leave behind files in your working directory and index, depending on what the state of the files were in the commit you branched off of:
The index and the working tree are adjusted as if you had previously run git checkout <start_point>
. This allows you to start a new history that records a set of paths similar to <start_point>
by easily running git commit -a
to make the root commit. — git checkout docs
What you'll want to do now is to completely clear the state of the branch, so that you're really starting again from a clean slate (run from top level folder):
git rm -rf .
This is the explanation from the docs:
If you want to start a disconnected history that records a set of paths that is totally different from the one of <start_point>
, then you should clear the index and the working tree right after creating the orphan branch by running git rm -rf .
from the top level of the working tree. Afterwards you will be ready to prepare your new files, repopulating the working tree, by copying them from elsewhere, extracting a tarball, etc.
Step 4: Recommit older work into new-master
Now you'll want to start commiting your older work into new-master
. When you're done, you'll rebase your old master on top.
I'm not exactly sure how your backup folders are organized, but I'm going to assume that each one is just a dated copy of your project folder, named in YYYY-MM-DD
format, e.g. 2013-01-30
. You can add the contents of each folder as a new commit manually, or you can write a script to automate the process.
Let's assume you have all your backup folders in a top folder called backup
that sits in the same directory as your main project folder. Then here is some pseudo-code for a script to automate the process of commiting each folder:
# Get backups and sort them from oldest to newest
backups = open("backups").toList().sort("date ascending")
for each (backup in backups)
copy files from backup to project folder
execute `git add "*"`
execute `git commit --date="#{backup.date}" --message="#{backup.date}"`
The script above will only add and modify files between commits. If any of your backups deleted, moved, or renamed files, the script won't record that. You'll need to write a more clever script to do that (maybe something that makes use of git diff
or some other diffing tool), or record those changes manually.
Step 5: Rebase old master
onto new-master
BEHOLD THE AWESOME POWER THAT IS GIT! Now we're going to REWRITE YOUR ENTIRE HISTORY! Recite these arcane words of power, and watch the magic happen!:
git rebase --onto new-master --root master
TA-DA! You might have to resolve conflicts between the last-commit of new-master
and the first-commit of master
, but other than that, that should be it!
The --root
flag to rebase
gets around the problem we were having earlier about the fact that—normally—rebase
won't operate on the first (root) commit of a Git repo. The --root
flag basically tells Git to include it in the rebase
:
Rebase all commits reachable from <branch>
, instead of limiting them with an <upstream>
. This allows you to rebase the root commit(s) on a branch. When used with --onto
, it will skip changes already contained in <newbase>
(instead of <upstream>
) whereas without --onto
it will operate on every change. — rebase docs
Step 6: Verify that final commit is still the same
Just to make sure we didn't mess any of the code up, we'll want to compare the current, final state of master
to its state before we did the rebase
. Either of the following commands will work (they're identical). While on the master
branch:
git diff orig_head # "Original" head
git diff master@{1} # Master at its 1st prior position
If you get no output from the diff
, then that means the final state of your rebased master
is identical to what it was before the rebase
. Good job!
NOTE: not sure what happens to merge commits...
Now the above steps will work fine if you have a perfectly linear history, i.e. you don't have any merge commits. I'm not sure if it will still work if you do. In my previous answer below I mentioned that using the --preserve-merges
flag with rebase
might help you keep those merges, but the docs mention that that flag also interacts with the --onto
and --root
flags:
When [--root
is] used together with both --onto
and --preserve-merges
, all root commits will be rewritten to have <newbase>
as parent instead. — rebase docs
I'm not exactly sure what that means at the moment...I might have to try it out and see what happens. Does it mean that if you have more than 1 root commit (like 10 for example), then 9 of those root commits will end up being rebased onto the last one acting as the new root? That doesn't seem like the behavior we would want. We just want to preserve merge commits for one root being rebased onto another.
Previous answer
MGA had the right idea with rebase
. I'm not sure if this will fix your problem, but maybe you need to make a new root commit in your repo, add your backup files as new commits on top of it, then rebase your old commit graph on top.
For example, git checkout
has an --orphan
flag according to the documentation:
--orphan <new_branch>
Create a new orphan branch, named <new_branch>
, started from <start_point>
and switch to it. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from all the other branches and commits.
So maybe you can try
git checkout --orphan <temp_branch>
Then commit your backup files to it. Then with temp_branch
checked out, do
git cherry-pick <first commit from master>
git rebase --onto temp_branch <first commit from master> master
I think you would probably need to cherry-pick
the first commit because the 2nd argument to rebase --onto
is the old base of the branch you want to rebase (in this case master
), and the old base isn't included. So that's why I figure you need to cherry-pick
it in order to get it, since it has no base commit itself, so you can't specify one for it in the rebase
.
Also note that if your commit history isn't linear (i.e. it has merge commits), then rebase
won't normally preserve those. It has a --preserve-merges
flag, but I've never used it before, so I'm not really sure of how it will work. From the docs:
-p
--preserve-merges
Instead of ignoring merges, try to recreate them.
This uses the --interactive
machinery internally, but combining it with the --interactive
option explicitly is generally not a good idea unless you know what you are doing (see BUGS below).
So, maybe all of that will work?
1) You can use git rebase
. In this case you could do the following:
First thing first, commit your old changes. Then:
$ git rebase --interactive master
This will open a file with your commits in a text editor. Just change the order of the commits (cut/paste your last commits to be the first ones, including the "pick" command on the left), save and close the editor.
At this point the rebase will begin. To continue when you're asked for confirmation, type:
$ git rebase --continue
Until it's all done. More information here.
2) I don't know any method to make this faster, but it should be pretty fast already.
3) To change the date of a commit you must change GIT_AUTHOR_DATE and GIT_COMMITTER_DATE.
For this there's a pretty example in the accepted answer for this StackOverflow question.
Hope it helps!