I'm teaching a class on Git. I don't know how, but one of my students managed to get three commits in a row that do not have a previous history! The student even managed to merge that line of three commits into master
(after the student researched and discovered --allow-unrelated-histories
), but in the history tree one can see that, rather than splitting off master
, those three commits are just hanging there, like a tail.
One thing that happened earlier is that in trying to clone the repository fooproj
into a separate directory fooproj-copy
, the student accidentally cloned it into a subdirectory of fooproj
, so that Bitbucket showed it as a subproject in the remote repository. I had the student remove this subdirectory fooproj-copy
, though, and push the new commit, so I thought all was well. I don't know how they managed to get commits with no parents.
So I said I would just wipe out those three commits (even though they had already been merged into master). I did a git reset --hard HEAD~1
and it walked up that line of commits. I did a git reset --hard HEAD~1
again and it kept going. But one more git reset --hard HEAD~1
and I had reached the end of the line; as there were no more parents on these three commits, I get this error:
fatal: ambiguous argument 'HEAD~1': unknown revision or path not in the working tree.
So normally if I want to throw away some commits, I just do a hard reset to the commit before that commit. But in this case there is no commit before the commits I want to throw away. How can I get rid of them?
First, from what you say you should probably not be teaching a class on Git. It's already extremely hard on its own, if it's taught by people who don't know it well I don't know what the students will do with it (I don't mean to attack you personally, you might mean well and have ended teaching that class for a good reason but I really think it's important that it be taught by people who know it well).
Now, to answer the question literally, to Remove a Git commit with no parent you just make sure that there aren't anymore any references to it
Done that git will remove its files automatically after a certain period; if you want to have it done right now (maybe because that commit, or series of commits, occupied a lot of space) you can use git gc --prune=all
.
So in your case, if as it seems those commits are not part of other branches and don't have any tags, you'd just have to reset your branch to some point of the history where the commit is not referenced.
However if what you want is solving the specific problem of your repository:
From whay you say it seems that the merge commit that the student made had the student's last commit as its first parent, and thus at least initially he merged the master into his branch.
The ~
commands follow the first parents, so when you did your first git reset --hard HEAD~1
you reset master to your student's line of commits (and with the following 2 commands you reached his first commit).
So now in that repository you might have lost all your other commits on master.
If you have another clone it's better that you just get back to it, otherwise your original line of commits might still be referenced by your reflog and thus still be there.
In what follows I assume that you haven't made other commits to master after your student's merge, it seems so from what you say.
It seems you are not using any graphical repository browser, if so type gitk --all
and use it to see exactly what the state of your branches/references is and to follow on it what you'll be doing (you have to refresh it manually with F5 after each modification). It's basically impossible to handle git well without a graphical browser, however many options you try to pass to git log.
Type git reflog
and see if it lists your commits. You'd need to find either your student's merge commit or the commit immediately before it.
To be sure of not losing anything else, first add a tag to where you currently are (git tag temp1
).
Then, if you found your student's merge commit, first tag it too (git tag temp-studentmerge <sha1-of-the-commit>
); in place of <sha1-of-the-commit> put the (abbreviated) sha1 of that commit, of course (it's that listed in the first column, on the left, of git reflog).
Then do git reset --hard temp-studentmerge
. Update gitk; you should now see again all your commits.
Now you have to reset to the correct parent of the merge; the easiest and safest option is to just look at its sha1 in your graphical browser, and do git reset --hard <sha1>
. Otherwise git reset --hard HEAD^2
should work, based on your description.
The ^<n> notation references the <n>th parent, while the ~<n> one references only the first parents.
Now check that everything's right, and then delete the temporary tags (git tag -d temp1
and git tag -d temp-studentmerge
) .
If you used gitk you'll still see the student's commits if you did a simle F5, do instead Shift-F5 (Reload) and you won't see them anymore.
The commit objects should actually still be in your repository somewhere, because git deletes the objects not referenced anymore only if they're older than a certain time (I'm actually not certain of the details).
If for some reason you want to remove any traces of them right now you can do git gc --prune=all
.