I wanted to download my project from github without useing the "git clone" command. So i followed this guide
git init
git add .
git commit -m "Add existing project files to Git"
git remote add origin https://github.com/cameronmcnz/example-website.git
git push -u -f origin master
After doing that, all of our 1000 commits where gone! Is there anything at all we can do to get our commits back?
Any help is preciated!
CodePudding user response:
Assuming you ran git init
in such a way as to create a new repository (rather than "reinitialize" an existing one), that's exactly what you should expect from that sequence of commands. (Side note: it is of course fine to experiment with things, to see what happens, but as a general rule, unless you're aware of larger constraints, never experiment with something particularly precious.)
After [using
git push --force
], all of our 1000 commits were gone!
Technically, they're not actually gone. You just can't find them.1 Git needs a commit's hash ID to look it up. To that extent, Git provides us—humans—with names: branch names, tag names, and other such names. These names have associated hash IDs; if they're commit hash IDs, that lets us find those commits directly by that name. If they're tag-object hash IDs, that lets us indirectly find a commit.2
Since each commit stores, in its metadata, a list—usually just one element long—of previous commits, we need only find the last commit in some chain this way. That commit will lead us (and Git) backwards through history. And this in turn is the answer to your question:
Is there anything at all we can do to get our commits back?
If you can find the hash ID of the latest commit on the branch name that you told GitHub to overwrite with this other commit hash ID, you can get those other commits back from GitHub. This depends on two things about GitHub:
- They always let you access any commit by its raw hash ID.3
- They never discard un-find-able commits.
Most Git implementations have tricks to permanently eject and forget commits that nobody can find by starting with some directly-find-able name like a branch or tag name. But GitHub in particular have this turned off for hosted repositories (for GitHub-specific reasons). This means that if you can find a hash ID, you can run:
git fetch origin <full-hash-id>:refs/heads/recovered
and now you have, in your local repository, a new branch named recovered
that points to the commit you found. If that's the last commit in the chain, you can use git log recovered
to see all your commits. If it's not the last one, you can keep looking for still-later commits.
Note that pull requests and other items exist in the GitHub-specific add-on database that GitHub maintain for your GitHub repository. This is what allows you to do things like issue management and pull requests in the first place. Importantly (for you), these will sometimes have hash IDs in them, or other links by which you can find the hash ID, after which you can use the above kind of git fetch
to obtain those commits.
Note also that if you find a hash ID of a commit from, say, a month ago, and use:
git fetch origin <hash-id>:refs/heads/recovered
to create a new recovered
branch, and later find another hash ID, you can run:
git fetch origin <new-hash-id>:recovered
(you no longer have to spell out refs/heads/recovered
, though you can if you want to) and your Git will figure out whether that's really a later commit and if so, "add on" to your recovered
branch. If it's an earlier commit, your Git will say that this git fetch
operation is not a "fast-forward" and refuse to drop commits from your recovered
branch.
This is/is-not a "fast forward" test is the same way that git push origin <name>
avoids "losing" commits from your GitHub repository. Using --force
, which exists for both fetch and push, overrides this protection. That's why the git push --force
you ran lost your commits: you overrode the usual protection.
1If Dr Livingstone is lost in Africa, does he really exist? Henry Morton Stanley says yes, but on the other hand, he found Livingstone. Later, he experimented with King Leopold II and the Congo river area, to tragic effect.
2An annotated tag object can refer to a tree or blob, or even to another annotated tag object, but this is unusual: most refer to commits.
3This is required to use the fancy new "partial clone" feature introduced in Git version 2.18 or so. (Pieces were in 2.17, and things didn't really work well until much more recently. But 2.18 is when the protocol was deliberately loosened. There are still rough edges with partial clones, but as of Git 2.32 or so they seem to be fairly usable.)