2

I have a repo where I tried to implement a feature based largely on one of two possible code bases. The problem is that the git repo I've been working in was actually the git repo of one of the projects, but not the one I built my work on top. I didn't think about it at the time, since I was just experimenting, but now it turns out we've built quite a lot on top of that experiment.

In the first commit on this new branch, I deleted all files, and pasted in the files from the other project. Then we built on top of this other project, and we're now about 100 commits in.

I would like to erase all history before that first branch commit where I deleted a ton of files and added a ton of other files, because that's where the history starts to make sense.

How can I do this?

Filip Haglund
  • 13,919
  • 13
  • 64
  • 113
  • for clarification, you have `A->Z` commits, you want `T->Z` commits. i.e.: Commits `A-T` you don't want. – ddavison Jan 12 '18 at 15:18
  • @Filip look at the [--orphan](https://git-scm.com/docs/git-checkout/1.7.3.1#git-checkout---orphan) option – Engineer Jan 12 '18 at 15:28
  • 2
    Possible duplicate of [Make the current commit the only (initial) commit in a Git repository?](https://stackoverflow.com/questions/9683279/make-the-current-commit-the-only-initial-commit-in-a-git-repository) – phd Jan 12 '18 at 15:38
  • @Engineer I didn't know that option. Very clean solution – rakwaht Jan 12 '18 at 15:38
  • @phd I don't think it is a duplicate, because in question https://stackoverflow.com/questions/9683279/make-the-current-commit-the-only-initial-commit-in-a-git-repository and most-noted answers, the point is to create a new repo with *only one commit*, while here the OP wants to keep the 100-or-so commits he has done in the new project – ErikMD Jan 12 '18 at 19:43

3 Answers3

1

One possibility is to use git rebase to rewrite your history. Note this is not a good practise if other people is working on this code and even more confusing and dangerous if you pushed this code in production.

Anyway if you want you can change your history using the following commands:

Get the commit ID of the oldest commit you want to delete. Use git log to find it.

Once you have you can rewrite the history:

$ git rebase -i <commitID_A>
pick idZ commit id Z
pick id1 commit id 1
pick id2 commit id 2
...
pick idT commit id T
...
pick id3 commit id 3
pick id4 commit id 4
pick idA commit id A

# Rebase idA onto idZ
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

So that's what you are going to see in you shell. From here are you can see from the help logs you can decide which commit will be picked and which one will be squashed. Just change in the list the word before the commit and go for it.

NOTE: you are rewriting all the commit between idA and HEAD if you do this so just be sure.

For more information just take a look here.

rakwaht
  • 3,666
  • 3
  • 28
  • 45
1

The rebase -i command mentioned by @rakwaht is indeed a useful tool for rewriting history (typically for rewriting the local history composed of a small number of commits that have not yet been pushed), but I believe this is not the best tool for the OP's use case.

A more practical solution would be to define a GraftPoint containing the SHA1 of the first commit you want in your history, and rely on git filter-branch to make the root commit change permanent (this step is a destructive operation, so make sure you have a backup of your repo beforehand)

This solution has already been proposed in this question on SO for another, similar use case.

To sum up, you could do (assuming you have a backup of your repo folder):

git checkout feature
# to checkout the branch of the new project

git log --graph --decorate --pretty=oneline
# to find the first commit to be kept (adding initial files in the new project)

echo 1234567890123456789012345678901234567890 > .git/info/grafts
# to define a GraftPoint with the appropriate SHA1

gitk
# to preview the impact of this GraftPoint

git filter-branch --prune-empty -- HEAD
# to rewrite the history of the current branch, as per the GraftPoint
# --prune-empty is optional and allows one to skip commits with empty content

rm .git/info/grafts
# to remove the GraftPoint and thus restore the other branches

gitk --all
# to inspect all branches
# --> note that the SHA1 of all commits in the feature branch have changed,
# so the new history is incompatible with the old one (w.r.t. merges, etc.)

In case you want the feature branch to be the only branch in your repo and forget everything else, you might want to delete the other branches, then purge the reflog and the corresponding commits by running these commands (beware: destructive operation):

git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git reflog expire --expire=now --all
git gc --prune=now --aggressive

cf. this part of git-filter-branch's documentation, as well as the web site of the BFG repo cleaner (which is an alternative of git filter-branch that enjoys better performance and may be useful in other use cases to forcibly rewrite Git history)

Hoping this helps

ErikMD
  • 13,377
  • 3
  • 35
  • 71
1

Don't work too hard. Just create a new commit with the existing tree object and make it the HEAD:

$ h=$( echo 'Initial commit' | git commit-tree HEAD^{tree} )
$ git reset --hard $h > /dev/null
William Pursell
  • 204,365
  • 48
  • 270
  • 300