This is not possible in general. "Was originally pushed to" is not even well-defined without picking some "original" and making it keep logs.
Here is an example. Suppose I create branches sneak
and gotcha
:
C <-- sneak
/
A--B <-- master
\
D <-- gotcha
Now if I git push
one or both of these branches, the receiving Git repository obtains the two commits C
and D
along with a request to update the names refs/heads/sneak
and refs/heads/gotcha
. So far, all seems good. But now I do this instead of pushing, or very rapidly after pushing—fast enough that you can't get in between to see what I am doing:
$ git push origin sneak:sneak gotcha:gotcha &&
> git checkout master &&
> git merge sneak gotcha &&
> git push origin master:master :sneak :gotcha
The git merge
makes an octopus merge (which of course I've arranged to have succeed, otherwise this takes too long for me to fool you :-) ). The push
step then sends commit E
to the server, along with a request to update refs/heads/master
to point to it, and to delete refs/heads/sneak
and refs/heads/gotcha
. The result is:
C
/ \
A--B---E <-- master
\ /
D
Which branches were C
and D
committed and/or pushed on? Well, we had that information on the server for about six milliseconds, before we overwrote and deleted it.
Worse, maybe the place I push is a push mirror, and the real server is further back. The push mirror may have had the information for as much as two or three seconds, plenty of time to grab it ... but the link between the push mirror and the real (end-point) server is acting up, and during those three seconds I overwrote it, so that the push mirror winds up sending commits C
, D
, and E
to the real server, with one single request, to update refs/heads/master
to point to commit E
.
Now, if we define "originally pushed to" as "sent to the push mirror", and we make the push mirror keep a log, the log will show that I originally asked for commit C
to go onto sneak
and commit D
to go onto gotcha
. Assuming the link-down glitch between the push mirror and the final central server, that log is the only place with this information. You can arrange a side channel for retrieving this, but none of that is built in to Git (even the logging is problematic: you can try to use Git's reflogs but they may not be fine-grained enough, if you care about multiple pushes per second and truly strict ordering).
Reflogs are not enabled by default for bare repositories (and push mirrors), but you can enable them with a simple git config
.
All that said ...
The main thing to worry about is the fact that commits can be on zero, one, or many branches.1 The trick is to not depend on branch names unless you are the one controlling those names. You have brief moments of control over branch names in pre-receive
and post-receive
hooks, but it's tricky to use.
Your best bet is not to rely on the names at all, but rather to require some separate indicator, such as a string embedded in the commit message itself (and you can have a pre-receive hook that checks this). Or, you can simply require that your tag names have a well-defined format:
production-v1.0.1
staging-v3.7
or whatever. The tag's name tells you what the intent of that specific commit is, and is quite independent of any containing branches.
1Commits that are on no branches are somewhat unusual, but easy to create: simply tag a tip-of-branch commit, then delete the branch name. You can push the tag and the commit goes to the receiving server with a tag, but no branch.