0

I have a box with a repo, we'll call it upstream. I have another box that cloned it a long time ago, we'll call it local.

A lot of things could have happened to local, it could have branches, tags, etc. I want to reset local to be in the same state as if it was a modern git clone. I don't want to just lose the changes in the working directory, I would like the states to be the same all around.

There are other solutions to this problem (usually which use cloning into a temp, nuking .git and moving over it) but I'm wondering if there is another way to do that with just git. I would prefer not to have to have to redownload what I don't need.

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • What's preventing you from just cloning it anew from upstream? What part of the old local repo do you try to preserve? – Romain Valeri Mar 29 '19 at 13:56
  • The working tree, which in our case includes utilities that are vital to keeping the system in a consistent state. (I know it's not a good idea) – Evan Carroll Mar 29 '19 at 14:15

2 Answers2

1

First, undo any single-branch-ness or shallowness unless the new clone would have been single-branch and/or shallow (in which case do any single-branch-ness or shallowness, but do the shallowing at the end of the process). Clean out any other nonstandard settings that accumulated in .git/config, other than ones you would have set manually in your git clone operation.

Then, if needed, rename one upstream / origin remote, removing all the others. Suppose, for instance, you had intended to use the default git clone remote name of origin, but the existing repository uses upstream1 for that and has a second remote upstream2. Remove upstream2 (with git remote remove) and rename upstream1 to origin (with git remote rename).

Then, run git fetch on the remote name you are keeping, whatever that is, with the --prune and --prune-tags options. The default is origin so if you're keeping the default you can just git fetch -p as this will mean git fetch =p origin. (I'm not 100% sure what happens to the upstream setting of the current branch, whatever it is, if it was some other remote, so you might want to just run git fetch -p origin to be safe in all cases.)

This next step assumes non-single-branch-ness. I'm not sure what origin/HEAD is when you use a single-branch clone of, say, branch X while origin's HEAD is set to master. If you're not worried about single-branch-ness (or you never use origin/HEAD) none of this really matters anyway, but I'm trying to be complete here.

Now run git remote set-head --auto on the remaining remote, e.g., git remote --set-head origin --auto. There's a slight race between the previous step and this one. If the upstream Git that you would have git cloned is moving its HEAD around from branch to branch all the time, that's sort of inevitable: which HEAD you got from them would depend on when you did your git clone. However, this will expose the difference between running git clone and running this sequence of commands. If the upstream Git isn't moving its HEAD around, there is no race here: their HEAD is stable between the git fetch and the git remote set-head.

Now choose the one local branch name you wish to have. Typically, in a fresh git clone with all the defaults, the one local branch you would have is the one they recommend with their HEAD, which is now copied to your origin/HEAD due to the git remote set-head. So read that out (git branch -r origin for instance, or git symbolic-ref refs/remotes/origin/HEAD) to see which branch that is. (It's probably master.) Check out that branch name—this should succeed even if your Git has to create it—and make sure its upstream is set to the appropriate remote-tracking name (e.g., origin/master), changing it if necessary. Use git reset --hard @{upstream} to:

  • move it, if git checkout didn't create it;
  • reset your index and work-tree

and then delete the others and use git clean -dfx to remove all directories and files that would not have been extracted from the initial git checkout that a git clone would have run as its last step.

You're now done, unless you wanted a shallow clone, in which case you may now need to en-shallow-ize your current repository. You may optionally run git gc --prune=all to clean up pack files.

Summary, minus all the fiddling with single-branch and shallow and such

If we can make a few reasonable assumptions the processes gets a lot simpler. The assumptions are:

  • no single-branch-ness, no shallowness, and just one remote origin that's already set correctly;
  • no local branches named origin/* to trip you up below;
  • no other screwy local settings to clean up (e.g., you didn't git config user.name in this repository); and
  • no desire to fuss with git remote set-head (you can do that any time though).

In this case, the process is this, which is pretty much cut-and-paste-able, just change master to whatever branch you wanted to keep:

git fetch -p --prune-tags
git checkout master             # or whatever branch you intended to keep
git branch --set-upstream-to=origin/master  # use correct origin/ name here
git reset --hard origin/master  # update work-tree
git clean -dfx                  # remove non-committed stuff from work-tree
git for-each-ref --format='%(refname:short)' refs/heads |
    while read name; do
        [ "$name" == "master" ] && continue   # skip the one to keep
        git branch -D "$name"                 # delete the others
    done

(the above is untested, so be careful!). The --set-upstream-to step is typically unnecessary (it will be a no-op), but I included it for completeness.

Please note that the git clean -dfx step potentially removes a lot of potentially-useful files. You said you wanted the state you'd have if you ran git clone, though, and none of those potentially-useful files would be copied by git clone. Since you are probably doing this to avoid this particular git clean type of effect, you probably want to omit, or at the least modify a lot, the git clean step.

torek
  • 448,244
  • 59
  • 642
  • 775
0

You can use this command to see your very first commit and it's hash:

git log --pretty=oneline | tail -1

Then run this command to reset to that hash:

git reset --hard [commit hash]
Mike Faber
  • 481
  • 5
  • 17