2

Below is the git history tree with the three commits highlighted that I want to keep. How can I get everything else to literally disappear as if they never existed?

Branches and merges and sh*t

I tried using answers from here but I kept getting the following:

fatal: Needed a single revision
Does not point to a valid commit: e147db0477dbb21e74615f4bfff06378b7d269c8^
Community
  • 1
  • 1
Nathan J.B.
  • 10,215
  • 3
  • 33
  • 41

2 Answers2

2

The first of your three highlighted commits is a merge. If you keep it, you must (really, it's not an option) keep the 5 commits you don't want. (A shallow clone can omit them until deepened but they remain logically present, even if not physically.)

Thus, what you need to do is make a new commit to which you can point master and origin/master, that is "as good" as the merge, but is not itself a merge, and simply has the middle of your three commits-to-retain as its parent.

The "cleaning up" and "frontend polish" commits can remain as-is; they just need a new commit that points back to them, that uses the same tree as the merge. (You can also keep the original commit message, or make a new one.) Git being git, there are a lot of ways to achieve this. The fewest-commands method is to use git commit-tree1 but let's look at a method I think is more straightforward and understandable.

First, we start by creating a new branch, pointing to the second to-keep commit, the "frontend polish" one. (This also assumes the current work-tree is clean.)

$ git branch newmaster master^2   # master^ if 1st parent 
$ git checkout newmaster

Now we want to make a new commit, using the source tree stored with the merge, that is not itself a merge. We begin by scrubbing away everything (so that any files removed during the merge, remain removed when we're done—if you're sure nothing got removed you can skip this step). Then, we check out all the files associated with the latest commit on master, i.e., the merge result. This assumes you're in the top level directory:

$ git rm -r .               # empties work-tree and index
$ git checkout master -- .  # repopulates work-tree and index

This form of checkout extracts from the given commit (master's tip-most commit) the given path(s)—in this case, ., which is the top level of the repository and therefore includes every path in the commit. Each extracted file is also entered into the index during the extraction, so we now have the new commit ready to go.

The only thing left to do is to recover the merge's message, if desired:

$ git log -1 master > /tmp/msg

(or similar), then:

$ git commit

This makes a new commit on newmaster, whose parent is the "frontend polish" commit, whose parent is "cleaning up", which has no more parents. Now you just need to get this to be branch master, and that's pretty trivial:

$ git branch -D master   # forcibly discard old master entirely
$ git branch -m master   # rename newmaster to master

At this point you can force-push to origin to replace master on the server (but be sure everyone else sharing the server's version is OK with this, and that there are no new commits there to worry about). When the server's master is updated, your origin/master (copied back from the server) will point to your master again (and at that point you can set your master's upstream to origin/master as usual).


For reference, this takes just 2 to 4 git commands, depending on how much you want to retain, how much you want to write from scratch, and how you count commands here:

$ git log -1 --format=%B master > /tmp/logmsg

(now edit /tmp/logmsg as desired)

$ newid=$(git commit-tree -p master^{tree} < /tmp/logmsg)

(at this point, make sure it worked, e.g., git show $newid)

$ git update-ref -m "reset to new non-merge tip" master $newid

(and now you have master pointing to the new copied commit that has only the one parent).

torek
  • 448,244
  • 59
  • 642
  • 775
  • I cloned a fresh new repo to try this on. Sadly, `git branch newmaster master~1` actually created a branch at the `merge branch 'local_backup'` commit. – Nathan J.B. Nov 10 '15 at 04:51
  • OK, I'm on my feet again. I used `git branch newmaster `. – Nathan J.B. Nov 10 '15 at 04:53
  • 1
    Ah, right, `master~1` must be the wrong one, we want `master^2` (2nd parent, not one-step-back-along-main-line). Or, raw SHA-1 ID always works :) – torek Nov 10 '15 at 04:54
  • Done. Thanks! I followed the first instructions (before you added the more advanced solution at the bottom). Only thing I did differently was `git rm -r .` which I misread at first but the end result was the same anyways. – Nathan J.B. Nov 10 '15 at 05:22
  • The `rm` step is usually not needed, it's more "just in case" than anything else. – torek Nov 10 '15 at 05:27
  • @torek Let me know (by a DM to VonC_ on twitter) if you want to join the "git team" (http://stackoverflow.com/teams/112/git, see http://meta.stackoverflow.com/q/309716/6309). I'll delete that comment later today. – VonC Nov 10 '15 at 08:32
  • @VonC I don't have a twitter account - I imagine you've noticed I have trouble keeping things under a few kbytes, much less 140 characters... :-) – torek Nov 10 '15 at 09:57
  • @torek Figures ;) Can you send me an email at vonc @ laposte dot net? – VonC Nov 10 '15 at 10:06
0

Although an old question and sufficiently answered, here is another possible option. A small script, that if called from within the repository part of the history of which you want to keep, would make a tmp directory and shallow clone the contents of the original repository.

DEEP_REPO=$(git rev-parse --show-toplevel)
TMP_REPO=$(git rev-parse --show-toplevel)/../tmp_repo

mkdir $TMP_REPO
git clone --depth 3 file://$DEEP_REPO $TMP_REPO -b branch_name

# remove the lines below if you want to keep both repositories
cd $TMP_REPO
rm -rf $DEEP_REPO
mkdir $DEEP_REPO
cp -r $TMP_REPO/. $DEEP_REPO
cd $DEEP_REPO
rm -rf $TMP_REPO

If you want to keep the original repository with the full history, truncate the script to the line that clones. Then the repository copy with the required history would be stored in the $TMP_REPO.

Also, you might want to edit the line of shallow cloning part: git clone --depth 3 file://$DEEP_REPO $TMP_REPO -b local_backup to fit your needs. Moreover, replace the branch name accordingly. In my use case git clone --depth 1 file://$DEEP_REPO $TMP_REPO was enough to keep just the last commit.

pac
  • 83
  • 8