Reputation: 1630
Say you are setting up a repository before it is made public, and in doing so you have made a number of commits in the process. When it comes time to change that repository to public, you don't want the original commits to be made public, you want the repository, and all its logs to instead start at the most recent commit. Is this possible?
Upvotes: 1
Views: 1553
Reputation: 212444
There are a few links to other questions that are probably canonical, but a quick glance through them didn't reveal what seems like the obvious solution.
A commit is an immutable object and one of its properties is its parent, and you cannot change a commit to make it not have a parent. However, you can simply create a new commit that uses the same tree. For example:
$ h=$( echo 'Initial commit' | git commit-tree HEAD^{tree} )
$ git reset --hard $h > /dev/null
$ git gc
This creates a new commit with the commit message "Initial commit" and stores its hash in $h
. The new commit has no parent. The reset then sets the current branch to point to this new commit. The git gc
cleans up any orphaned objects. If you want to delete all the old cruft, delete all your tags and branches before you do the git gc
so that all other commits are garbage collected.
Upvotes: 3
Reputation: 4667
The simplest and cleanest way to do that is to create a new branch starting from the current commit and without any history:
git checkout --orphan new-master
git commit -m "Initial commit"
This will let you keep both old and new history in parallel in the same repository (i.e. multiple roots). When you push to a public remote, you just have to make sure that you always push only new branches/tags and never any of old branches/tags. If you prefer, you can also rename branches so that your "new-master" is called "master" like usual.
This works very well, but it's easy to make a mistake, and even git implementation might still upload some of your "old" suff due to internal optimizations. A skilled attacker might be able to recover some of the old commits even though they are not directly visible. To make sure none of old stuff gets left behind, you can delete all the old branches, tags, remotes and reflog and then do a git gc --aggressive
. Similarly, you can split your "new" and "old" stuff into separate repositories - keep your old history private, new history clean and still be able to work with both of them individually.
Upvotes: 0
Reputation: 535617
Maybe I'm being very dense, but I would just make sure I've checked out the desired initial state (e.g. git switch master
) and then throw the repository part away (rm -rf .git
). Now just start over with git init
, git add .
, git commit -minitial
. Push that to a new public repo.
Upvotes: 1
Reputation: 489203
There's a sort of fundamental error in the question, because it assumes there's exactly one root commit. In fact, "rootness" is a property of a commit: a commit is a root commit if and only if it has has no parent commit.
Whenever you make a new commit with git commit
, Git will make that new commit use the current commit's hash ID as its (first, and usually only) parent commit.
As a special case—one that is necessary in a new, totally-empty repository that has no commits yet—Git allows you to be on what it calls, variously, an unborn branch or an orphan branch. The "orphan" word is the one that appears in the git checkout
and git switch
commands:
git checkout --orphan new-branch
and:
git switch --orphan new-branch
for instance. The "unborn" adjective is probably the superior one, though, as it describes the actual state, when you're on this mode: these two commands do not actually create the new branch at all. When you are in this mode, you are on a branch that does not exist!
When in this mode—when you're on an unborn branch—the git commit
command will create a new root commit, i.e., one with no parent. The creation of this new root commit causes the branch to spring into being, with the new branch name now identifying the new root commit. So one way to achieve your desired result is:
git checkout master # if needed; use main if appropriate, etc
git checkout --orphan new
git commit
and then rename the old master
or main
out of the way, or delete it entirely, then rename new
to master
or main
.
This uses a bit of a trick, which I'll describe in the next paragraph, and achieves the same result as William Pursell's answer, which is also fine. Note that you must use git checkout --orphan
here, not git switch --orphan
, and that's part of the trick.
The trick here is that git commit
builds the new commit from whatever is in Git's index. The git checkout --orphan
command does not touch Git's index, so what's in the index is whatever was in the index just a moment earlier, before you ran git checkout --orphan
. That's why we may need an initial git checkout master
or git checkout main
: to fill in Git's index (and your working tree).
The git switch --orphan
command has the side effect of emptying out Git's index (and your working tree). So this is good for creating a new empty commit: one that does not re-use the files from the current commit. The git checkout --orphan
command doesn't empty Git's index, so it is good for creating a new commit that exactly matches the current commit. Since neither command is one people use every day, these subtleties may go unnoticed.
Most repositories probably have just one root commit. Any non-empty (and non-shallow1) repository has at least one root commit, but the number of root commits is only limited by the total number of commits.
1A shallow clone is one that omits one or more commits that were present in the non-shallow Git repository that was the ultimate source of these commits. To do so, Git inserts a file marking "graft points", and doesn't bother getting their parents. The resulting commits have parents, but parts of Git pretend that they don't, so these shallow-graft-points act as both root commits and non-root commits, depending on what code is trying to do what with them. Other than when working with a shallow repository for its space and/or time saving aspect, though, it's probably best just to ignore this complication.
Upvotes: 1