J...S
J...S

Reputation: 5207

Remove all commits prior to a commit with `git filter-repo`

Is there a way to 'remove' all commits prior to a specific commit all the way to the root commit? I figured git filter-repo does this kind of stuff?

Although I wasn't sure if it will work when root commit is involved.

My situation is like this:

I got a copy of a repo where I started working in but ended up doing something quite different that the new files have no relation to what I started with.

  Unwanted commits             Desired commits
+-------------------+     +-----------------------+
|                   |     |                       |
O1 -> O2 -> ... -> On -> N1 -> N2 -> N3 -> ... -> Nm

where On commits are the old commits that I wish to get rid of and Nm commits are the ones I wish to keep.

This is what I wish to make it:

      Desired commits
 +-----------------------+
 |                       |
N1 -> N2 -> N3 -> ... -> Nm

where N1 would become root instead of `O1.

I want to keep the git history of only the N commits.

Is there a way to do this?

Previously, I tried to ask git filter-repo to keep the commits of only the desired set of files with this:

(Saw that in the example section of the manual. https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html#_path_based_filtering)

git filter-repo --path file1 --path file2 --path dir/

But that removed the files that I wanted and the git log became kind of empty. I guess I misunderstood something.

Upvotes: 1

Views: 410

Answers (2)

TTT
TTT

Reputation: 28869

First off, note that matt's answer solves your ask without using git-filter-repo. That is a good answer if you don't have many commits to rewrite (simply because rebasing is relatively slow compared to git-filter-repo), and if your history is linear, or you desire it to become linear, or if you wish to preserve merge commits and you didn't have historical conflicts.

A more general solution is to use a similar strategy as this answer:

  1. Create the new root commit:
git checkout --orphan new-main <commit-ID-of-N1>
# if you want the same commit message as N1:
git commit --reuse-message=<commit-ID-of-N1>
# if you want a different commit message:
git commit -m "New root commit"
  1. Replace N1 with the new commit:
git replace <commit-ID-of-N1> @
  1. Make the replace permanent using git-filter-repo:
git filter-repo --replace-refs update-or-add --force

The advantages of this method are that it's extremely fast (even if you have hundreds of thousands of commits), and it isn't possible to have merge conflicts because you aren't changing the graph of the portion of history you're keeping.

Upvotes: 2

matt
matt

Reputation: 534893

Suppose we have this history for my main branch main:

bf89ae6 (HEAD -> main) f
de493f8 e
87f0019 d
45ddf08 c
e26add9 b
a01bc2c a

And let's say I want to cut off all of the history before "d" (i.e. 87f0019). Then I say:

git checkout --orphan temp 87f0019
git commit -m 'a fresh start'
git rebase --onto temp 87f0019 main

Result:

5e6216c (HEAD -> main) f
e0c4157 e
cfbac28 (temp) a fresh start

Okay, those are entirely new commits, but when you're changing history, that's inevitable. Also, note that the first commit contains all the work up to that point; it has basically been squashed into a single initial commit. That might or might not be what you want.

Upvotes: 3

Related Questions