You need to create a refspec referring to the commit in question, and push the refspec.
The reason behind this is a bit messy, as it were. The actual set of objects transferred in a push
(or any other object-transporting sequence) is not necessarily limited to what you might think. Sometimes a push will push "unused" repo objects, as the synchronization between the client (doing the push) and server (receiving an object pack) is specified loosely. It guarantees that all required objects will make it there, but may push some that are not necessary. It could actually take more communication to figure out "what's necessary" than just to push everything, especially with all the compression going on in the send/receive sequence.
Thus, what happens internally in a "push" is that the client sends over a big ball (specifically, a "thin pack") full of objects, and then—after those objects are safely ensconced in the server's repo—sends over a set of "reference labels":
refs/heads/zorg = object 00ac1d1f1ab1ec0ffee5c01ec0idf1dd1edeedee
refs/tags/skin = object 5011d1f1ab1ebac1110515be11e5be111c05ed0e
and so on. The server runs these ref-updates through the git pre-receive and update hooks to verify that they are OK, and if they are, stores them in the server's refs. (The hooks can inspect these objects, if desired. This lets a hook check whether tags are "lightweight", referring directly to commits, or "annotated", referring to tag-objects in the repo, for instance.)
I will add here that I think your question implies a misunderstanding of how branches actually work. A branch is an "implied" data structure. Git has two underlying concepts that coordinate to create a "branch": it has the branch-tip labels, which name one single commit; and it has commits, which each carry zero or more "parent commit" IDs. What makes, e.g., master
a branch is the branch-tip-name (refs/heads/master
). But that just gets you one single commit, the tip of the branch. To find what's "on" the branch, you start there and look at that commit's parent or parents. Each parent commit is also "on" the master
branch at that instant, and each parents' parents are on it, and so on. If you have:
M1 -- M2 -- M3 -- M4 <-- master
\
B1 -- B2 -- B3 <-- branchB
\ /
D1 -- D2 <-- develB
then commit B3
is "on" branchB
because it is the tip of branchB
, and D2
and B2
are also both "on" the branch because B3
has both of those commits as its parents.
If you don't want D2
on branchB
after all, but you do want D1
on it, you must make a new (and quite different) commit—let's call it B4
—that has B2
and D1
as its parents:
M1 -- M2 -- M3 -- M4 <-- master
\
| ------B4 <-- branchB
| / |
B1 -- B2 -- B3 | <-- branchB-old [or abandoned entirely]
\ / |
D1 -- D2 | <-- develB
\ /
----------
Again branchB
simply names a tip-of-branch commit, but this one is different from B3
.
Pushing the above (the mess with B4
) may actually push commit B3
to the server, but as long as there is no label for it, no one will see it, and it will eventually be garbage-collected off the server.