876

I currently have a local Git repository, which I push to a Github repository.

The local repository has ~10 commits, and the Github repository is a synchronised duplicate of this.

What I'd like to do is remove ALL the version history from the local Git repository, so the current contents of the repository appear as the only commit (and therefore older versions of files within the repository are not stored).

I'd then like to push these changes to Github.

I have investigated Git rebase, but this appears to be more suited to removing specific versions. Another potential solution is to delete the local repo, and create a new one - though this would probably create a lot of work!

ETA: There are specific directories / files that are untracked - if possible I would like to maintain the untracking of these files.

jsingh
  • 1,256
  • 13
  • 24
kaese
  • 10,249
  • 8
  • 29
  • 35
  • 6
    See also http://stackoverflow.com/questions/435646/how-do-i-combine-the-first-two-commits-of-a-git-repository ("How do I combine the first two commits of a Git repository?") – Anonymoose Mar 13 '12 at 11:56
  • Possibly related: [How do I combine the first two commits of a Git repository?](http://stackoverflow.com/q/435646/456814). –  May 15 '14 at 08:59
  • and this: [How to squash all git commits into one?](https://stackoverflow.com/questions/1657017/how-to-squash-all-git-commits-into-one) – ryenus Nov 17 '17 at 04:15
  • See also https://stackoverflow.com/a/66091688/8079868 –  Dec 14 '21 at 05:15

18 Answers18

1127

Here's the brute-force approach. It also removes the configuration of the repository.

Note: This does NOT work if the repository has submodules! If you are using submodules, you should use e.g. interactive rebase

Step 1: remove all history (Make sure you have a backup, this cannot be reverted)

cat .git/config  # save your <github-uri> somewhere
rm -rf .git

Step 2: reconstruct the Git repo with only the current content

Before step 2 if you have not set up init.defaultBranch configuration then, please do it via git config --global init.defaultBranch <branch-name> you may choose main as <branch-name> in the current example

git init
git add .
git commit -m "Initial commit"

Step 3: push to GitHub.

git remote add origin <github-uri>
git push -u --force origin main
mfaani
  • 33,269
  • 19
  • 164
  • 293
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • 5
    Thanks larsmans - I have opted to use this as my solution. Though initialising the Git repo loses record of untracked files in the old repo, this is probably a simpler solution for my problem. – kaese Mar 13 '12 at 12:14
  • 6
    @kaese: I think your `.gitignore` should handle those, right? – Fred Foo Mar 13 '12 at 12:44
  • 55
    Save your .git/config before, and restore it after. – lalebarde Apr 19 '14 at 08:35
  • @lalebarde If you restore .git/config after `git commit -m "Initial commit"` then you can probably skip the `git remote add ...` part, assuming that was already in your config, and move straight on to pushing. It worked for me. – Buttle Butkus Nov 17 '15 at 03:51
  • Deleting the `.git` folder may cause problems in your git repository. If you want to delete all your commit history but keep the code in its current state, it is very safe to do it as in the following: http://stackoverflow.com/a/26000395/2019689 – NoNameProvided Jan 16 '16 at 09:26
  • 45
    Be careful with this if you are trying to remove sensitive data: the presence of only a single commit in the newly pushed master branch is **misleading - the history will still exist** it just won't be accessible from that branch. If you have tags, for example, which point to older commits, these commits will be accessible. In fact, for anyone with a bit of git foo, I'm sure that after this git push, they will still be able to recover all history from the GitHub repository - and if you have other branches or tags, then they don't even need much git foo. – Robert Muil May 03 '16 at 09:05
  • 2
    So many bad answers in the world and after an hour I finally got this to do my bidding! –  Apr 23 '17 at 16:52
  • push to GitLab - error: http://stackoverflow.com/questions/32246503/how-to-fix-you-are-not-allowed-to-push-code-to-protected-branches-on-this-proje/32267118 – Ivan Rave Apr 24 '17 at 13:04
  • 1
    @RobertMuil is it safe now? Could you edit it if not? – beppe9000 May 17 '17 at 13:28
  • @beppe9000 I don't know for sure, but I don't think it is: the wiki hasn't changed since I made my previous comment. I won't edit because I don't know an easy solution apart from completely deleting *all repos*... – Robert Muil May 22 '17 at 16:16
  • Does this send only the modified portions of the repository? e.g. if I have a large file F already in the top commit and do this, will the forced push send F's contents again over the wire? – Zuzu Corneliu Apr 19 '18 at 13:23
  • 1
    Above still not removed all history what I had to change was one extra command at the end: git push origin --mirror – Kris79 Nov 23 '18 at 14:39
  • So this answer is not a real solution? – Jonathan Jul 30 '20 at 12:18
  • Using @Zeelot's answer below was the easiest and least destructive way to purge the history. It should be the selected answer – Richard Barker Mar 31 '21 at 17:08
  • why not use `mv .git .git-old` which would be safer, then remove .git-old when finished? Also, you can initially run `git ls-files > files.txt` and then add them back with a simple `cat files.txt | awk '{print "git add " $0 }' | sh` – mwag Dec 19 '21 at 09:01
  • Hi, related to this full history rewrite, is there some ways to actually forbid this as part of repositories permissions in bitbucket cloud ? – bugske Jun 24 '22 at 08:56
  • please make sure to fire this command before git init ```git config --global init.defaultBranch main``` – nomadSK25 Jul 31 '22 at 20:09
825

The only solution that works for me (and keeps submodules working) is

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

Deleting .git/ always causes huge issues when I have submodules. Using git rebase --root would somehow cause conflicts for me (and take long since I had a lot of history).

MatthewG
  • 8,583
  • 2
  • 25
  • 27
Zeelot
  • 8,412
  • 1
  • 14
  • 4
  • 74
    this should be the correct answer! just add a `git push -f origin master` as the last op and sun will shine again on your fresh repo! :) – gru Jan 07 '14 at 13:30
  • This solutions works locally. What if I have a remote master? Will have side effects ? – Jone Polvora Apr 23 '14 at 22:00
  • 2
    Does this not keep old commits around? – Brad Apr 26 '14 at 05:15
  • 6
    @JonePolvora git fetch; git reset --hard origin/master http://stackoverflow.com/questions/4785107/git-pull-from-remote-can-i-force-it-to-overwrite-rather-than-report-conflicts – echo May 02 '14 at 17:41
  • 7
    after doing this, will the repo free space? – Inuart Aug 28 '14 at 15:32
  • 3
    @Brad: I was wondering the same thing. Seems that --orphan only gets the last commit so that the remaining ones are thrown away when the branch is deleted. Reference: http://git-scm.com/docs/git-checkout – Hirnhamster Nov 23 '14 at 10:24
  • Good for times when you're not using Github – noɥʇʎԀʎzɐɹƆ Jun 27 '15 at 17:44
  • 2
    This doesn't work with a remote repo? I've used the script, but with no success. My .git folder is still huge. I've also tried scripts by @gru and echo. Any advice? – pir Jul 06 '15 at 09:57
  • The cleanest way to achieve the desired result. – dsclose Mar 08 '16 at 14:56
  • 1
    Tried the accepted answer exactly as it is written and it didn't work. Tried this answer exactly as it is written, worked like a charm. This should be the accepted answer! – abagh0703 Sep 10 '16 at 14:31
  • 7
    Git will keep the old files around for a while, to get rid of them run `git gc --aggressive --prune=all`. In addition, git will continue to store history for any commits that are referenced with branches or tags. To check, run `git tag -l` and `git branch -v`, then delete any you find. Also double check your remote with `git ls-remote`, you may need to delete remote tags/branches as well or when you fetch you will get all the linked files again. – Jason Goemaat Sep 15 '16 at 07:48
  • 11
    I believe you should add @JasonGoemaat 's suggestion as the last line to your answer. Without `git gc --aggressive --prune all` the whole point of losing history would be missed. – Tuncay Göncüoğlu Oct 22 '16 at 12:19
  • 6
    Instead of deleting original _master_ branch, one can rename it `git branch -m master old_master` and set push mode to current-branch-only `git config --local push.default current`. This way one can keep historical commits in separate branch and work in sync with repo in the fresh master. – Seweryn Niemiec Nov 09 '16 at 21:06
  • 3
    This does not work. I do get this error: git branch -M master -f error: refname refs/heads/newBranch not found – mgiaco May 07 '18 at 11:48
  • 1
    This should be the accepted answer. Deleting .git and similar stuff is unsafe since it does not guarantee the same tree state after recommiting the files. – Mike76 Sep 20 '19 at 08:39
  • Important note for thos working with GitLab, BitBucket, etc... https://stackoverflow.com/questions/48029867 – Royi Oct 08 '19 at 10:21
  • Perfect solution, thank you! Worth mentioning that when I did it on a protected master branch I got a "pre hook declined error" I had to unprotect the branch and retry. Worked like a charm. – Tarik.J Jan 02 '20 at 02:55
  • This was the only working solution in my case! Thanks! – JE_Muc Aug 21 '20 at 12:02
  • 1
    This does the job but wouldn't free disk space until issuing `git reflog expire --expire-unreachable=all --all` followed by the mentioned git gc (it might do that some time later automatically, dunno) – kjyv Oct 07 '20 at 11:42
122

This is my favoured approach:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

This will create a new branch with one commit that adds everything in HEAD. It doesn't alter anything else, so it's completely safe.

dan_waterworth
  • 6,261
  • 1
  • 30
  • 41
  • 6
    Best approach! Clear, and do the work. Additionally, i rename the branch with a lot of changes from "master" to "local-work" and "new_branch_name" to "master". In master, do following: git -m local-changes git branch -m local-changes git checkout new_branch_name git branch -m master – Valtoni Boaventura Feb 09 '15 at 12:46
  • 1
    This looks really short and sleek, the only thing I don't understand or haven't seen yet is HEAD^{tree}, could somebody explain? Apart from that I'd read this as "create new branch from given commit, created by creating a new commit-object with given commit-message from ___" – TomKeegasi Feb 15 '17 at 08:10
  • 6
    The definitive place to look for answers to questions about git reference syntax is in the `git-rev-parse` docs. What's happening here is `git-commit-tree` requires a reference to a tree (a snapshot of the repo), but `HEAD` is a revision. To find the tree associated with a commit we use the `^{}` form. – dan_waterworth Feb 15 '17 at 20:48
  • 2
    Nice answer. Works well. Finally say `git push --force new_branch_name:` – Felipe Alvarez Dec 11 '17 at 22:01
  • 2
    And everything in one line: `git branch newbranch $(echo "commit message" | git commit-tree HEAD^{tree}) | git push --force origin newbranch:master` – Peroxy Oct 13 '20 at 17:57
36

The other option, which could turn out to be a lot of work if you have a lot of commits, is an interactive rebase (assuming your git version is >=1.7.12):git rebase --root -i

When presented with a list of commits in your editor:

  • Change "pick" to "reword" for the first commit
  • Change "pick" to "fixup" every other commit

Save and close. Git will start rebasing.

At the end you would have a new root commit that is a combination of all the ones that came after it.

The advantage is that you don't have to delete your repository and if you have second thoughts you always have a fallback.

If you really do want to nuke your history, reset master to this commit and delete all other branches.

Carl
  • 43,122
  • 10
  • 80
  • 104
  • 1
    After rebase completed, I can not push: `error: failed to push some refs to ` – Begueradj Mar 12 '19 at 14:37
  • @Begueradj if you've already pushed the branch you rebased, then you will need to force push `git push --force-with-lease`. force-with-lease is used because it is less destructive than --force. – Carl Mar 14 '19 at 20:04
21

Variant of larsmans's proposed method:

Save your untrackfiles list:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

Save your git configuration:

mv .git/config /tmp/

Then perform larsmans's first steps:

rm -rf .git
git init
git add .

Restore your config:

mv /tmp/config .git/

Untrack you untracked files:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

Then commit:

git commit -m "Initial commit"

And finally push to your repository:

git push -u --force origin master
emotality
  • 12,795
  • 4
  • 39
  • 60
lalebarde
  • 1,684
  • 1
  • 21
  • 36
8

Below is a script adapted from @Zeelot 's answer. It should remove the history from all branches, not just the master branch:

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

It worked for my purposes (I am not using submodules).

Shafique Jamal
  • 1,550
  • 3
  • 21
  • 45
  • 4
    I think you forgot to force push master to complete the procedure. – not2qubit Mar 07 '19 at 13:08
  • 2
    I had to make a slight modification. `git branch` will include an asterisk next to your checked out branch, which will then be globbed, causing it to resolve to all files or folders as if those were branch names too. Instead, I used `git branch --format="%(refname:lstrip=2)"` which gave me just the branch names. – Ben Richards Aug 21 '19 at 21:36
  • @not2qubit: Thanks for this. What would be the exact command? `git push --force origin master`, or `git push --force-with-lease`? Apparently the latter is safer (see https://stackoverflow.com/questions/5509543/how-do-i-properly-force-a-git-push) – Shafique Jamal Aug 21 '19 at 22:35
  • @BenRichards. Interesting. I'll try this again at some point with a folder that matches a branch name to test it, then update the answer. Thanks. – Shafique Jamal Aug 21 '19 at 22:37
6

This deletes the history on the master branch (you might want to make a backup before running the commands):

git branch tmp_branch $(echo "commit message" | git commit-tree HEAD^{tree})
git checkout tmp_branch
git branch -D master
git branch -m master
git push -f --set-upstream origin master

This is based on the answer from @dan_waterworth.

Tom Dörr
  • 859
  • 10
  • 22
5

You could use shallow clones (git > 1.9):

git clone --depth depth remote-url

Further reading: http://blogs.atlassian.com/2014/05/handle-big-repositories-git/

Matthias M
  • 12,906
  • 17
  • 87
  • 116
5

Just delete the Github repo and create a new one. By far the fastest, easiest and safest approach. After all, what do you have to gain carrying out all those commands in the accepted solution when all you want is the master branch with a single commit?

Johann
  • 27,536
  • 39
  • 165
  • 279
4

git filter-branch is the major-surgery tool.

git filter-branch --parent-filter true -- @^!

--parent-filter gets the parents on stdin and should print the rewritten parents on stdout; unix true exits successfully and prints nothing, so: no parents. @^! is Git shorthand for "the head commit but not any of its parents". Then delete all the other refs and push at leisure.

jthill
  • 55,082
  • 5
  • 77
  • 137
4

What I'd like to do is remove ALL the version history from the local Git repository, so the current contents of the repository appear as the only commit (and therefore older versions of files within the repository are not stored).

A more conceptual answer:

git automatically garbage collects old commits if no tags/branches/refs point to them. So you simply have to remove all tags/branches and create a new orphan commit, associated with any branch - by convention you would let the branch master point to that commit.

The old, unreachable commits will then never again be seen by anyone unless they go digging with low-level git commands. If that is enough for you, I would just stop there and let the automatic GC do it's job whenever it wishes to. If you want to get rid of them right away, you can use git gc (possibly with --aggressive --prune=all). For the remote git repository, there's no way for you to force that though, unless you have shell access to their file system.

AnoE
  • 8,048
  • 1
  • 21
  • 36
3

Here are the steps to clear out the history of a Github repository

First, remove the history from .git

rm -rf .git

Now, recreate the git repos from the current content only

git init
git add .
git commit -m "Initial commit"

Push to the Github remote repos ensuring you overwrite history


git remote add origin git@github.com:<YOUR ACCOUNT>/<YOUR REPOS>.git
git push -u --force origin master
Kapilrc
  • 1,362
  • 15
  • 11
3

All the answers are awesome but I would like to put another approach with simple commands

git clone --depth 1 <remote-url> .
git commit --amend -m <commit message you want>
git push --force
SAMUEL
  • 8,098
  • 3
  • 42
  • 42
2

The method below is exactly reproducible, so there's no need to run clone again if both sides were consistent, just run the script on the other side too.

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

If you then want to clean it up, try this script:

http://sam.nipl.net/b/git-gc-all-ferocious

I wrote a script which "kills history" for each branch in the repository:

http://sam.nipl.net/b/git-kill-history

see also: http://sam.nipl.net/b/confirm

Sam Watkins
  • 7,819
  • 3
  • 38
  • 38
  • 1
    Thanks for this. Just FYI: your script to kill the history for each branch could use some updating - it gives the following errors: `git-hash: not found` and `Support for /info/grafts is deprecated` – Shafique Jamal Feb 12 '19 at 04:46
  • 1
    @ShafiqueJamal, thanks, the little "git-hash" script is `git log HEAD~${1:-0} -n1 --format=%H`, here, http://sam.aiki.info/b/git-hash It would be better to put it all in one script for public consumption. If I ever use it again, I might figure out how to do it with the new feature that replaces "grafts". – Sam Watkins Feb 18 '19 at 06:18
2

Here you go:

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

Also hosted here: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
  • 9,096
  • 3
  • 52
  • 44
  • Gah! Dont make me provide my unhidden, unprotected password at the command line! Also, the output of git branch is typically poorly suited for scripting. You may want to look at the plumbing tools. – D. Ben Knoble Sep 01 '19 at 14:41
-1

I solved a similar issue by just deleting the .git folder from my project and reintegrating with version control through IntelliJ. Note: The .git folder is hidden. You can view it in the terminal with ls -a , and then remove it using rm -rf .git .

JB Lovell
  • 88
  • 5
-1

For that use Shallow Clone command git clone --depth 1 URL - It will clones only the current HEAD of the repository

kkarki
  • 1
-3

To remove the last commit from git, you can simply run

git reset --hard HEAD^ 

If you are removing multiple commits from the top, you can run

git reset --hard HEAD~2 

to remove the last two commits. You can increase the number to remove even more commits.

More info here.

Git tutoturial here provides help on how to purge repository:

you want to remove the file from history and add it to the .gitignore to ensure it is not accidentally re-committed. For our examples, we're going to remove Rakefile from the GitHub gem repository.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

Now that we've erased the file from history, let's ensure that we don't accidentally commit it again.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

If you're happy with the state of the repository, you need to force-push the changes to overwrite the remote repository.

git push origin master --force
kiriloff
  • 25,609
  • 37
  • 148
  • 229
  • 9
    Remove files or commits from the repository has absolutely no relation with the question (which asks to remove history, a completely different thing). The OP wants a clean history but wants to preserve current state of the repository. – Victor Schröder May 22 '15 at 09:07
  • 1
    this does not produce the result asked in the question. you are discarding all changes after the commit you keep last and losing all changes since then, but the question asks to keep current files and drop history. – Tuncay Göncüoğlu Oct 22 '16 at 12:28