A long time ago, I set up a repo using git --separate-git-dir because I did want the repo inside the working directory. Unfortunately, that separate (presumably "bare"?) repo has been lost to hard drive failure, and I want to rebuild it using the contents of a remote I had added (which was pushed to frequently).
I tried by recreating a new, empty git repo:
git init --separate-git-dir=/desired/path/to/bare/git/repo
This of course creates a .git
file in the working directory with the contents, gitdir: /desired/path/to/bare/git/repo
Then, I did:
git remote add network ssh://host/path/to/repo
git fetch network
But, if I run git status
, I get
On branch master
No commits yet
I only ever used (and pushed) the master branch. I am hoping to get this repo to a state WITHOUT MODIFYING THE WORKING DIRECTORY where I can type git status
and hopefully it only sees the changes between the current, unmodified working directory and the last commit pushed to the remote, which should now be in my local bare?
I'm really just trying to pick up where I left off.
Thank you.
TL;DR: what remains is to create a master
branch, probably with git branch master network/master
(perhaps with various flags; see below), and fill in the index, probably using git read-tree
. Hence:
git branch master network/master
git read-tree master
is probably sufficient.
Let me turn my comments above into an actual answer now that I have a bit of time to spare. We've noted that --separate-git-dir
doesn't make the repository bare—a bare repository is one with no work-tree—it just puts the work-tree and repository (.git/*
files) in two different locations in the file system. To make this function, Git adds a .git
file in the work-tree, containing the path name of the repository itself, as you noted.
So, in this case, that repository itself has been damaged or destroyed. You have a work-tree with no repository. You've created a new, empty repository using another git init --separate-git-dir=...
and run the commands:
git remote add network ssh://host/path/to/repo
(I would probably have named the remote origin
rather than network
but there's nothing wrong with network
, it's just a bit unconventional.)
What you needed to do next is populate the repository itself with commits, and you did that:
git fetch network
Your repository itself (in whichever directory) now has some set of commits, copied from the Git repository at the URL at which you ran git fetch
.
At this point almost everything is normal. Two things are missing:
There are no local branches, in spite of the current branch being the one named master
. The branch master
doesn't exist.
The index—the collection of files that would go into the next commit you would make—is empty.
If you were to make a commit now, that would create a commit with no parent commit, and with no content (the empty tree), and create the branch master
in the process, but that's pretty clearly not what you want. Instead, you probably want:
To create the local name master
to match the remote-tracking name origin/master
that points to the final commit of the branch master
in the network
repository from which you copied all its commits. To do that, use:
git branch master network/master
You can use anything you like as the final argument, as long as it names any actual commit: a raw hash ID will suffice, or something like network/master^
, or network/master~3
, or whatever. If you do use the name network/master
, Git recognizes that this is a remote-tracking name.1 As a consequence, git branch
sets this remote-tracking name as the upstream of the new (local) branch name master
.
More precisely, the automatic upstream setting is the default action for git branch
here (and for some forms of git checkout
as well). You can configure Git to change the default, or you can add command line flags to override whatever default you have or have not set.
To prevent this upstream-setting, use git branch --no-track master network/master
. To force the upstream-setting, use git branch --track master network/master
, which you can abbreviate as git branch --track network/master
.
Whether and when to set the upstream setting is up to you. You can always change it later using git branch --set-upstream
, or remove it later using git branch --unset-upstream
.
Now that master
exists—or you can do this before creating master
, but I'd do it afterward as it just feels simpler and easier to get right—you will want to populate your index from the commit you've selected as your current commit. Normally—in a situation other than this "repair broken Git repository" case—we'd do steps 1 and 2 all together at once using git checkout
, but you want to do this without disturbing the current work-tree, and git checkout
disturbs the current work-tree. So we do this as a separate step 2, using one of Git's lower level plumbing commands:2
git read-tree master
(or git read-tree HEAD
or git read-tree @
if you want to type less: all three do the same thing). This simply reads the named commit into the index, without doing anything else at all: it replaces whatever was in the index (which was nothing) with whatever is in the named commit.
After doing the git branch
and git read-tree
, git status
will be able to compare the current commit—the one named by HEAD
/ master
—with the current index contents—they will match of course—and then compare the current index contents with the current work-tree contents. These will match or differ based on which commit you chose when setting up your own master
in step 1, and in any case you'll be ready to git add
and make new commits if you like.
1A remote-tracking name is any name whose full spelling starts with refs/remotes/
. In this case network/master
is really refs/remotes/network/master
. Git documentation mostly calls these remote-tracking branch names but the word "branch" here is, I think, more misleading than not, so I omit it.
These names exist in your Git repository and are automatically created and updated based on the branch names that your Git gets from the other Git—the one at the URL stored under the name network
—whenever your Git does a git fetch
from the Git at network
. A successful git push network
also updates any branches that they set based on your Git's requests.
2The distinction between plumbing and porcelain in Git is really in terms of who is meant to use a command: a porcelain command is meant to be user-friendly and goal-oriented,3 and a plumbing command is meant to be a command that a porcelain command might use as one of a series of such commands (and/or in combination with other system utilities) in order to accomplish some actual goal. Thus, plumbing commands tend to be extra-specific and mechanism-oriented (git read-tree
, "fill index from commit"; git rev-parse
, "turn human-readable name into internal hash ID"; git update-ref
, "write raw hash ID into arbitrary reference").
3Or perhaps "less actively user-hostile". :-) See also https://git-man-page-generator.lokaltog.net/