19

I wish to get rid of a lot of my repo's old history, so I did a shallow clone to get the last 50 commits only:

git clone --depth=50 https://my.repo

That worked OK, but when I create a new Gitlab repo and try to push it I get an error:

git remote remove origin
git remote add origin https://my.repo
git push -u origin --all
[...]
 ! [remote rejected] master -> master (shallow update not allowed)

But I only want these 50 commits to be in my new repo's history. How can I tell git that it should just treat these 50 commits as the only commits in the new repo?

Jez
  • 27,951
  • 32
  • 136
  • 233
  • 2
    https://stackoverflow.com/questions/28983842/remote-rejected-shallow-update-not-allowed-after-changing-git-remote-url – Geno Chen Jun 22 '18 at 18:12

2 Answers2

35

Here's what I ended up doing - it worked perfectly. Note that I was moving from my old host (Bitbucket) to my new one (Gitlab). My comments are above the commands:

# First, shallow-clone the old repo to the depth we want to keep
git clone --depth=50 https://...@bitbucket.org/....git

# Go into the directory of the clone
cd clonedrepo

# Once in the clone's repo directory, remove the old origin
git remote remove origin

# Store the hash of the oldest commit (ie. in this case, the 50th) in a var
START_COMMIT=$(git rev-list master|tail -n 1)

# Checkout the oldest commit; detached HEAD
git checkout $START_COMMIT

# Create a new orphaned branch, which will be temporary
git checkout --orphan temp_branch

# Commit the initial commit for our new truncated history; it will be the state of the tree at the time of the oldest commit (the 50th)
git commit -m "Initial commit"

# Now that we have that initial commit, we're ready to replay all the other commits on top of it, in order, so rebase master onto it, except for the oldest commit whose parents don't exist in the shallow clone... it has been replaced by our 'initial commit'
git rebase --onto temp_branch $START_COMMIT master

# We're now ready to push this to the new remote repo... add the remote...
git remote add origin https://gitlab.com/....git

# ... and push.  We don't need to push the temp branch, only master, the beginning of whose commit chain will be our 'initial commit'
git push -u origin master

After that, I did a fresh clone of the new repo and I got just the master branch with the 50 most recent commits - exactly what I wanted! :-) Commit history has gone from 250MB to 50MB. Woot.

Jez
  • 27,951
  • 32
  • 136
  • 233
  • 1
    Excellent solution. For the new “initial commit” that you create, you may want to overwrite `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL` and `GIT_AUTHOR_DATE` and set them to the original values of the commit that you’re about to replace. – caw Mar 30 '19 at 17:17
  • 2
    Works great, however it seems that all existing tags on these rebased commits are lost - any idea how to fix that? – kreys May 04 '20 at 15:46
  • Interesting idea. I don't know if this will work for multiple branches where a subset of those branches depend on each other's history. In this "multiple branch dependency graph" scenario, doing a transplant (--onto) rebase on each would create duplicate commits I think, almost like cherry-picks. – solstice333 Mar 12 '22 at 03:33
  • actually, you could pull something off for the "multiple branch dependency graph" scenario, but it'd just be really tedious transplanting with `rebase --onto`... – solstice333 Mar 12 '22 at 03:52
  • @solstice333: yes, that's exactly what happens. To use the procedure without a lot of pain (i.e., merge conflicts), you need to cut the graph at a commit with no other active branches. I found my cut point using `git log --graph`, then had to be careful about the --depth value so it ended at exactly that commit. – cobbr2 Jul 27 '22 at 00:17
8

You can't push a shallow clone into a new remote. You have to unshallow your clone first. Do a fetch from the old remote with the --unshallow parameter:

git fetch --unshallow old

and you should be able to push to your new remote. Note that you will need to add back your old remote first to fetch from it.

BUT...

That isn't what you want though. To remove the history from a full clone you would need to use git rebase to effectively remove the old history. There are other methods but since you only want the last 50 commits, this will be the easiest solution. Assuming the master branch:

git rebase --onto master~y master~x master

where x is the number of the first commit to be kept, and y is the number of the first commit you want to remove. At this point you can push to your new remote with only the history you want to keep. Note you will need to enumerate the commit numbers in git log yourself, as it wants an index (starting at 1) and not a commit hash.


Take care, as rewriting history can be a dangerous thing in Git, and has other implications you will need to consider. Also make sure not to push the changes to the old remote unless you want to remove the old history there as well.

Source: https://www.clock.co.uk/insight/deleting-a-git-commit

codewario
  • 19,553
  • 20
  • 90
  • 159
  • With my shallow repo, could I use `git checkout --orphan new_master`, then `START_COMMIT=$(git rev-list master|tail -n 1)`, then `git checkout $START_COMMIT -- .` and `git commit -m "Initial commit"` to make `new_master` initial commit the same as the repo state at the beginning of the shallow `master`, and then rebase `master` onto `new_master`? Would that work? – Jez Jun 22 '18 at 20:58
  • I'm not sure to be honest, but it looks like you already confirmed in your answer. I rarely rewrite repo history but there are cases when I've wanted to do the same thing you are, keep X number of recent commits and remove anything older. Personally I like running one command but as I indicated, and you discovered, there is more than one solution to this problem :) – codewario Jun 28 '18 at 19:19