0

The answer on this question shows how to selectively push local commits, but it seems like those are local commits on the tip.

If I have merged in new changes from the origin such that they are interspersed with my commits, (and the last commit is the 'merged' message,) is there anything I can do to push only some of these commits? Or do I need to branch before merging and reset/stash the commits I want to leave out?

Community
  • 1
  • 1
Michael Chinen
  • 17,737
  • 5
  • 33
  • 45

1 Answers1

1

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.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Wow, that's a lot more complicated than I thought it would be. Would you mind writing out the example commands that would do this? – Michael Chinen Sep 17 '13 at 17:50
  • The way to figure out what to run is to start by drawing the commit graph you have now (use `gitk --all` or `git log --graph --oneline --decorate --all` or similar to see what you have), then figure out the commit graph you want—whiteboards or pen(cil)/paper are very handy here—and only then start considering running `git ...`. It's not trivial because you're taking what "really happened" (actual history) and coming up with a prettier "pretend this happened instead" (new "history"). – torek Sep 17 '13 at 20:39