Reputation: 40325
This project has only a master
branch, so all work is being done there.
I mistakenly committed a typo, and while I could commit an undo, I thought I'd try just removing the commit altogether.
The commit hash is dbcbf96b
, and ded82215
is the commit right before it. So, working from these instructions, I did this:
git rebase -i ded82215
This brought up the "todo" list in an editor, containing only the following, as expected:
pick dbcbf96 Writer update.
Presumably following the instructions in the comments of that generated file, I changed that line to:
drop dbcbf96 Writer update.
I saved the file and closed the editor. It then said:
Successfully rebased and updated refs/heads/master.
I then checked on GitLab (what I'm using to host this) expecting to see the commit gone, but it was not, it was still there.
I did git status
to see what action it recommended and it yielded:
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
That kinda makes sense, but as for the rebase, I'm not sure what happened. Now I did git pull
to update the local repo but it was all just right back where I started, right back at dbcbf96b
with the local file still containing the change, the local repo up to date, and GitLab still showing the revision. The pull
command yielded:
Updating ded8221..dbcbf96
So my rebase attempt did nothing at all. What did I miss here?
So I tried git push -f
between steps 4 and 5 as recommended by the answer below and it failed, yielding:
Total 0 (delta 0), reused 0 (delta 0)
remote: GitLab: You are not allowed to force push code to a protected branch on this project.
To https://gitlab.com/...
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://gitlab.com/...'
I then tried git push
just for grins and it also failed, yielding:
To https://gitlab.com/...
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'https://gitlab.com/...'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
That kind of makes sense, too, I suppose that's what -f
is for. The hints in the second bit again recommended git pull
, which I already know isn't the right action here.
Upvotes: 6
Views: 9211
Reputation: 488003
Git is built to add new stuff (where "stuff" means "commits") rather than to take away stuff. That's why you must use a force-push: your push would "take away stuff", i.e., drop your commit.
You gave your rebase an explicit instruction, "drop this commit", so it did (with a bunch of airbags and safety belts that let you restore it, usually for at least 30 days). A regular git push
won't let you drop a commit; only a force push can.
Because a force push can lose commits, some servers offer a "protected branch" mode where some/many/all people are disallowed from force-pushing at all. This is what you have encountered now. To successfully force-push, you must set yourself up as allowed to do it—usually, by temporarily unprotecting the branch, then re-protecting it after the push.
Make sure you are not dropping anyone else's commits at the same time! Branch names are, in effect, just labels on commits. It's the commits themselves that form the history-of-commits in a repository. Each commit has, inside itself, the ID of its parent commit. We say that each commit "points back to" its parent:
... <- commit 1234567 <-- branch
To add new commits, Git simply advances the branch "forward" (even though all the internal Git arrows point "backwards") to the newer commit. For instance if we start with:
... <-B <-C <--branch
we add new commit D
by making a D
that points to C
, then making the name branch
point to D
:
... <- B <- C <- D <--branch
To drop the D
, we make branch
point to C
again, but if someone else has come along and made new commit E
, the picture now looks like this:
...--B--C--D--E <-- branch
When we now force branch
to point to C
instead, both D
and E
are lost:
...--B--C <-- branch
\
D--E [abandoned]
With git push
, this is (almost) all you can do. (There is one safety-belt available, called a "force with lease", where you have your Git tell their Git: "I believe your name branch
points to commit D
. If so, force it to point to C
instead. If not, give me an error." But there's nothing fancier, as their is within your own repository. In your own repository, git rebase -i
offers the ability to copy selected commits to new, but different, commits, so that you can drop intermediates. git push
has only the force options.)
Because of these, Git also offers the ability to "reverse" a commit: take some existing commit, figure out what it did, and do exactly the opposite—put back any file that was removed; remove any file that was added; remove lines added and add lines removed—as a new commit. You can therefore back out any older commit in a newer commit, and this merely adds on to the history. For instance, if, in the:
A--B--C--D--E
sequence, we find that B
was bad, we can tell Git "make a new commit F
that is the reverse of B
", giving:
A--B--C--D--E--F
which "does the same thing" as if we never had either B
or F
in the sequence, but does so without having to disturb C
through E
. The command that backs out an entire commit by adding its reversal is git revert
.
Your own Git repository is just that: yours. You can do anything you want to it, including dropping (abandoning) commits at the end of a chain, or copying commits (via git rebase
) to drop intermediates by copying all the commits past that point. This lets you "edit your history".
But, each of these commits has a unique ID. That's the big ugly hash ID Git shows you from time to time. The unique ID is a cryptographic checksum of everything that went into the commit—so "editing history" results in all-new IDs. Gits exchange (push and fetch) commits by their IDs, so once you make your commits available to another Git instance—such as with git push
—you have published those commits. You can attempt to "un-publish" them, but with who knows what success. It's a bit like trying to delete a tweet: someone probably already captured it.
This doesn't mean "never try to change published history", just "be aware that if you do try to change published history, it might not work, and anyone who's started to use that published history may be annoyed."
Upvotes: 3
Reputation: 171263
So my rebase attempt did nothing at all. What did I miss here?
That's not true, it definitely did do something.
You rebased your local repo, which doesn't do anything to the remote one on GitLab. So the remote one still has the unwanted commit, and when you git pull
you fetch it and merge it back into your local repo.
After the rebase you would have needed to do a git push -f
to force-push the altered local repo to GitLab, to remove the bad commit in both repos.
Upvotes: 5