{"thread":{"id":"43753","subject":"Re: [DRAFT] Branching and merging with git","startedAt":"2006-11-17T01:09:21Z","lastAt":"2006-11-17T05:55:45Z","messageCount":3,"participants":["Junio C Hamano","linux@horizon.com"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"298513","messageId":"7vac2q281q.fsf@assigned-by-dhcp.cox.net","threadId":"43753","inReplyTo":"20061116221701.4499.qmail@science.horizon.com","subject":"Re: [DRAFT] Branching and merging with git","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-11-17T01:09:21Z","receivedAt":"2006-11-17T01:09:21Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"linux@horizon.com writes:\n\n> One outstanding problem with git's man pages is that often the most detail\n> is in the command page that was written first, not the user-friendly\n> one that you should use.\n\nThis is a very important point to remember not for users but for\nus in git community.  Thanks for writing it down.\n\n> * Git's representation of history\n>...\n> And then there are commits which have multiple parents.  Two is most\n> common, but git allows many more.  (There's a limit of sixteen in the\n> source code, and the most anyone's ever used in real life is 12, and\n> that was generally regarded as overdoing it.  Google on \"doedecapus\"\n> for discussion of it.)\n\nDodecapus would find a few, no hits on doedecapus ;-).\n\n> * Deleting branches\n>\n> \"git branch -d <head>\" is safe.  It deletes the given <head>, but first\n> it checks that the commit is reachable some other way.  That is, you\n> merged the branch in somewhere, or you never did any edits on that branch.\n\nIt is not \"somewhere\" but \"in the current branch\", so in a sense\nit is a bit stricter than that.  While on 'master' \"branch -d\ntopic\" would not remove the topic branch head if it is not fully\nmerged to my 'master' so that is a reasonable safety measure,\nbut when I am on 'next' it will happily remove it.  It is\nrecoverable because it is reachable from 'next', though.\n\n> * Remote tags\n>\n> TODO: Figure out how remote tags work, under what circumstances\n> they are fetched, and what git does if there are conflicts.\n\nOne bug in my previous response is that I said we do this only\nwhen the command was invoked with shorthand remote name.  Not\nso.  We do this only when we use tracking branches.\n\nThe reason is because 'git pull $url $branch' (typical Linus's\nuse) and 'git pull' (defaulting to 'origin' and using the\ntracking branch mapping stored in .git/remotes/origin prepared\nby git-clone) are sign of very different workflows.  The former\ntends to be a one-shot event while the latter is most often\nsynchronizing with either an upstream or a common distribution\npoint (i.e. shared central repostiory).  When you are fetching\nfrom somebody in a one-shot manner, most likely as a part of\n'pull', you do not want to get the tag the other person has made\nto mark his private work in progress.  But in the latter case,\nthe other end is where everybody who works in the same area\nfetches from, and sharing the tags found there among the\ndevelopers by default is desirable, and more importantly there\nis no risk of accidentally getting private tags, since the other\nend is a public distribution point and by definition should not\nhave private tags that would clutter your refs/tags hierarchy.\n\n> * Exchanging work with other repositories, part II: git-push\n>...\n> You have to merge the changes locally, and re-try the push when you've\n> got a new head that includes the most recently pushed work as an ancestor.\n>\n> This is exactly like \"cvs commit\" not working if your recent checkout\n> wasn't the (current) tip of the branch, but git can upload more than\n> one commit.\n>\n> The simplest way to resolve the conflict is to merge the remote head with\n> your local head.  This is easiest if you have different local branches\n> for fetching the remote repository and for pushing to it.\n>\n> That is, you have one head that just tracks the master repository's\n> main branch, and another that you add your work to, and push from.\n> This makes merging simpler when there are conflicts.\n\nHere you _might_ want to mention an alternative workflow that\nuses rebase, which seems to be the way Wine folks run their\nproject.  Talking about all the different possibilities tends to\ncloud things and may not add value to the document, so I am just\nmentioning it as a possibility but I do not know if talking\nabout rebase is useful in the context of this document.\n\n> * Merging (finally!)\n>\n> I went through everything else first because the most common merge case\n> is local changes with remote changes.  Not that you can't merge two\n> branches of your own, but you don't need to do that nearly as often.\n>\n> The primitive that does the merging is called (guess what?) git-merge.\n> And you can use that if you want.  If you want to create a so-called\n> octopus merge, with more than two parents, you have to.\n\nThis is not true; \"git pull . topicA topicB topicC\" works as\nexpected.  But we probably would not want to even talk about\nOctopus in a document like this.  It is a curosity, and\nsometimes tends to make histories even less cluttered, but\notherwise it does not add much value.\n\n> However, it's usually easier to use the git-pull wrapper.  This merges\n> the changes from some other branch into the current HEAD and generates\n> a commit message automatically.\n>\n> git-merge lets you specify the commit message (rather than generating it\n> automatically) and use a non-HEAD destination branch, but those options\n> are usually more annoying than useful.\n\nI haven't tried for a long time, but I do not think non-HEAD\ndestination even works at all.  It might be better not to even\nmention git-merge at this point of the document.\n\n> * How merging operates\n>...\n> If all three of a given file in O, A, B are different, then the three\n> versions are pulled into the index file, called \"stage 1\", \"stage 2\",\n> and \"stage 3\", and a merge strategy driver is called to resolve the mess.\n> Git then uses the classic line-based three-way merge, looking for isolated\n> changes and applying the same rules as for files when two of the source\n> files are the same in some range.\n\nYou might also want to mention that recursive first 3-way merges\nthe renames.  If O->A renames a path while O->B keeps it, the\nresulting stages are written under the new name.\n\n> * When merging goes wrong\n>...\n> and the result of \"git diff\" is\n>\n> diff --cc hello.c\n> index 4b7f550,948a5f8..0000000\n> --- a/hello.c\n> +++ b/hello.c\n> @@@ -3,5 -3,6 +3,10 @@@\n>   int\n>   main(void)\n>   {\n> ++<<<<<<< HEAD/hello.c\n>  +      printf(\"Goodbye, cruel world!\");\n> ++=======\n> +       printf(\"Hello, world!\\n\");\n> +       return 0;\n> ++>>>>>>> edadc53fc7a8aef2a672a4fa9d09aa16f4e14706/hello.c\n>   }\n>\n> Notice how this is not a standard diff!  It has two columns of diff\n> symbols, and shows the difference from each of the ancestors to the\n> current hello.c contents.  I can also use \"git diff -1\" to compare\n> against the common ancestor, or \"-2\" or \"-3\" to compare against each of\n> the merged copies individually.\n\nAnother tool to help the user decide how the mess should be\nsorted out is \"git log --merge -- $path\".  It gives the logs of\ncommits that touched the path while the two branches were forked.\n\n> * Cherry picking\n>...\n> You van get the list of commits on a branch with git-log or git-rev-list,\n\ns/van/can/\n\n> * Rebasing\n>...\n> Git-rebase can also help you divide up work.  Suppose you've mixed up\n> development of two features in the current HEAD, a branch called \"dev\".\n\nAncestry graph before and after this procedure would help the\nreader a lot here.\n\n>...\n> This will find all patches that are in dev and not in dev1,\n> apply them on top of master, and call the result dev2.\n\n> * Experimenting with merging\n>...\n> $ git status\n> Hey, look, lots of interesting stuff.  Particularly, see\n> # Changed but not updated:\n> #   (use git-update-index to mark for commit)\n> #\n> #       unmerged: Makefile\n> #       modified: Makefile\n> #       unmerged: builtin.h\n> #       modified: builtin.h\n> #       unmerged: git.c\n> #       modified: git.c\n>\n> The \"unmerged\" (a.k.a. \"staged\") files are ones that need manual resolution.\n>\n> (I notice that update-index.c isn't listed, despite being mentioned\n> as a conflict in the message.  Can someone explain that?)\n\nThey were conflicts during the virtual ancestor computation by\nrecursive (the merge between 'a' and 'b' commits in your earlier\nexample).  When a virtual ancestor is created, it can textually\nhave conflicted merge, but that is recorded along with conflict\nmarkers without manual resolving for obvious reasons.  If two\nbranches that use the virtual ancestor modifies the conflicted\nregion the same way (because they needed to resolve that\nconflict in their branch), the final 3-way merge that uses the\nvirtual ancestor as the merge-base will replace that conflicted\nregion with their changes.  This \"even conflict markers can be\neliminated by a merge resolution\" behaviour is what inspired\ngit-rerere, by the way.\n\nIf you are using this particular commit as an example, you might\nalso want to tell your readers about:\n\n\tgit show -M 3f69d405\n\n(-M is there to make the output more readable, because this\nmerge involved a few renames).\n"},{"id":"297927","messageId":"20061117031747.30672.qmail@science.horizon.com","threadId":"43753","inReplyTo":"7vac2q281q.fsf@assigned-by-dhcp.cox.net","subject":"Re: [DRAFT] Branching and merging with git","fromName":"","fromEmail":"linux@horizon.com","sentAt":"2006-11-17T03:17:47Z","receivedAt":"2006-11-17T03:17:47Z","isPatch":false,"sender":{"key":"linux@horizon.com","avatar":null},"body":"Overall, thank you.  I'm trying to merge all your comments\ninto the document to make it better, but there are enough that\nit's taking me a while.\n\n>> One outstanding problem with git's man pages is that often the most detail\n>> is in the command page that was written first, not the user-friendly\n>> one that you should use.\n> \n> This is a very important point to remember not for users but for\n> us in git community.  Thanks for writing it down.\n\nThere's a great example coming up, in the git-show\nexample you gave me.  That's a very sparse man page...\n\n> Dodecapus would find a few, no hits on doedecapus ;-).\n\nWups, thanks.\n\n>> * Deleting branches\n>>\n>> \"git branch -d <head>\" is safe.  It deletes the given <head>, but first\n>> it checks that the commit is reachable some other way.  That is, you\n>> merged the branch in somewhere, or you never did any edits on that branch.\n> \n> It is not \"somewhere\" but \"in the current branch\", so in a sense\n> it is a bit stricter than that.  While on 'master' \"branch -d\n> topic\" would not remove the topic branch head if it is not fully\n> merged to my 'master' so that is a reasonable safety measure,\n> but when I am on 'next' it will happily remove it.  It is\n> recoverable because it is reachable from 'next', though.\n\nOh!  Thanks for the info.  The limitation makes a certain\namount of sense, and as I'd never run into it, I'm not going to\ncomplain.\n\n> The reason is because 'git pull $url $branch' (typical Linus's\n> use) and 'git pull' (defaulting to 'origin' and using the\n> tracking branch mapping stored in .git/remotes/origin prepared\n> by git-clone) are sign of very different workflows.  The former\n> tends to be a one-shot event while the latter is most often\n> synchronizing with either an upstream or a common distribution\n> point (i.e. shared central repostiory).  When you are fetching\n> from somebody in a one-shot manner, most likely as a part of\n> 'pull', you do not want to get the tag the other person has made\n> to mark his private work in progress.  But in the latter case,\n> the other end is where everybody who works in the same area\n> fetches from, and sharing the tags found there among the\n> developers by default is desirable, and more importantly there\n> is no risk of accidentally getting private tags, since the other\n> end is a public distribution point and by definition should not\n> have private tags that would clutter your refs/tags hierarchy.\n\nI'll work this in somehow, thanks.\n\n> Here you _might_ want to mention an alternative workflow that\n> uses rebase, which seems to be the way Wine folks run their\n> project.  Talking about all the different possibilities tends to\n> cloud things and may not add value to the document, so I am just\n> mentioning it as a possibility but I do not know if talking\n> about rebase is useful in the context of this document.\n\nDone, thanks.\n\n>> The primitive that does the merging is called (guess what?) git-merge.\n>> And you can use that if you want.  If you want to create a so-called\n>> octopus merge, with more than two parents, you have to.\n> \n> This is not true; \"git pull . topicA topicB topicC\" works as\n> expected.  But we probably would not want to even talk about\n> Octopus in a document like this.  It is a curosity, and\n> sometimes tends to make histories even less cluttered, but\n> otherwise it does not add much value.\n\nSorry; the SYNOPSIS line for git-pull had me fooled.\n\n>> However, it's usually easier to use the git-pull wrapper.  This merges\n>> the changes from some other branch into the current HEAD and generates\n>> a commit message automatically.\n>>\n>> git-merge lets you specify the commit message (rather than generating it\n>> automatically) and use a non-HEAD destination branch, but those options\n>> are usually more annoying than useful.\n> \n> I haven't tried for a long time, but I do not think non-HEAD\n> destination even works at all.  It might be better not to even\n> mention git-merge at this point of the document.\n\nWell, I want to at least say \"you think so, wouldn't you?\"\n\n>> * How merging operates\n> \n> You might also want to mention that recursive first 3-way merges\n> the renames.  If O->A renames a path while O->B keeps it, the\n> resulting stages are written under the new name.\n\nThanks!  I wondered how that happened!\n\n>> * When merging goes wrong\n\n> Another tool to help the user decide how the mess should be\n> sorted out is \"git log --merge -- $path\".  It gives the logs of\n> commits that touched the path while the two branches were forked.\n\nThe things I never knew about...\n\n>> Git-rebase can also help you divide up work.  Suppose you've mixed up\n>> development of two features in the current HEAD, a branch called \"dev\".\n> \n> Ancestry graph before and after this procedure would help the\n> reader a lot here.\n\nI figured the (excellent) pictures in git-rebase would save me the\ntrouble, but yeah, I suppose so.\n\n> They were conflicts during the virtual ancestor computation by\n> recursive (the merge between 'a' and 'b' commits in your earlier\n> example).  When a virtual ancestor is created, it can textually\n> have conflicted merge, but that is recorded along with conflict\n> markers without manual resolving for obvious reasons.  If two\n> branches that use the virtual ancestor modifies the conflicted\n> region the same way (because they needed to resolve that\n> conflict in their branch), the final 3-way merge that uses the\n> virtual ancestor as the merge-base will replace that conflicted\n> region with their changes.  This \"even conflict markers can be\n> eliminated by a merge resolution\" behaviour is what inspired\n> git-rerere, by the way.\n\nCool, thanks!  I added mention of this.\n\n> If you are using this particular commit as an example, you might\n> also want to tell your readers about:\n> \n> \tgit show -M 3f69d405\n> \n> (-M is there to make the output more readable, because this\n> merge involved a few renames).\n\nI'm wondering what the heck that does!  I get a super-short diff with\nno mention of any renames at all.  Is this passed on to git-diff-tree?\nWhat does \"detect renames\" mean if it doesn't tell me about them?\nI'm actually confused.\n\ndiff --cc builtin-read-tree.c\nindex ec40d01,99e7c75..716f792\n--- a/builtin-read-tree.c\n+++ b/builtin-read-tree.c\n@@@ -9,9 -9,9 +9,10 @@@\n  \n  #include \"object.h\"\n  #include \"tree.h\"\n+ #include \"cache-tree.h\"\n  #include <sys/time.h>\n  #include <signal.h>\n +#include \"builtin.h\"\n  \n  static int reset = 0;\n"},{"id":"297925","messageId":"7vr6w2vcpq.fsf@assigned-by-dhcp.cox.net","threadId":"43753","inReplyTo":"20061117031747.30672.qmail@science.horizon.com","subject":"Re: [DRAFT] Branching and merging with git","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-11-17T05:55:45Z","receivedAt":"2006-11-17T05:55:45Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"linux@horizon.com writes:\n\n>>> * When merging goes wrong\n>\n>> Another tool to help the user decide how the mess should be\n>> sorted out is \"git log --merge -- $path\".  It gives the logs of\n>> commits that touched the path while the two branches were forked.\n>\n> The things I never knew about...\n\nOf course \"git log -p --merge -- $path\" would give the patch\ntext as well.\n\n>> If you are using this particular commit as an example, you might\n>> also want to tell your readers about:\n>> \n>> \tgit show -M 3f69d405\n>> \n>> (-M is there to make the output more readable, because this\n>> merge involved a few renames).\n>\n> I'm wondering what the heck that does!  I get a super-short diff with\n> no mention of any renames at all.  Is this passed on to git-diff-tree?\n> What does \"detect renames\" mean if it doesn't tell me about them?\n> I'm actually confused.\n\n\"show $merge\" is really \"diff-tree --cc -p $merge\".  So first I\nshould (not necessarily \"you should to the readers of this\ndocument\") talk about three ways to describe a merge commit with\ntextual diffs.\n\n(1) N independent diffs between each of the parents and the child.\n\n    We could get this with\n\n    \tgit diff-tree -m -p $merge\n\n    but it is mostly useless, because very often many paths in a\n    merge are truly trivial and the version from one of the\n    parents is taken verbatim, whole file.  When looking at the\n    development history, the real reason of the change is found\n    in earlier log for that parent, and not in the merge in\n    question.\n\n    The diff between the child and its first parent is somewhat\n    useful, because it represents the damage inflicted on his\n    branch the person saw when he made the merge.  For this\n    reason, \"--stat\" gives the graph for the first-parent diff\n    for a merge.  But otherwise \"diff-tree -p\" by default stays\n    silent about merges because it is not that useful, and that\n    is why the above asks for the \"useless output ;-)\" with an\n    explicit \"-m\".\n\n(2) Uncompressed \"combined diff\" between all parents and the child.\n\n    We can get this with:\n\n\tgit diff-tree -c -p $merge\n\n    This gives a combined diff that shows all the files parents\n    and child disagreed (in other words, if the resulting file\n    matches verbatim with one of the parents, it is not shown).\n    This is already useful by reducing the clutter of truly\n    trivial merges, compared to (1) above, but most clean merges\n    take either first or second parent's version verbatim for\n    each hunk (but not necessarily taking all hunks from the\n    same parent) and these hunks are not very interesting.\n\n    Because \"-c\" explicitly tells something special to be done\n    for a merge, you do not need to say \"-m\" for the above\n    command (giving -m does not hurt, but is not necessary).\n    Uncompressed combined diff is a still per-file affair, so by\n    default the above gives the output in the \"raw\" format, and\n    that is why the above command still says \"-p\".\n\n(3) Compressed \"combined diff\".  This is what \"show\" gives by\n    default, and we can get this with:\n\n\tgit diff-tree --cc $merge\n\n    The difference from \"-c -p\" output is that this reduces the\n    clutter further by dropping uninteresting hunks from the\n    output.  If all the changes in a hunk is from only one\n    parent, or the changes are the same from all but one parent,\n    the hunk is dropped from the output (looking at dodecapus\n    with --cc is interesting for this reason).\n\n    Like \"-c\", this explicitly asks a magic to be done on a\n    merge, so \"-m\" is implied.  Unlike \"-c\", this operation is\n    per-hunk and \"raw\" format (which is inherently per file)\n    does not make any sense.  Because \"raw\" is impossible, \"-p\"\n    is also implied.\n\nBoth (2) and (3) are \"combined\".  It combines the diffs with\neach parent; for N-parent merge, it combines N diffs into one.\n\nWhat -M/-C does is to see which path in each parent is used to\ndiff against a path in the child.  For example, the stat part\nof:\n\n\tgit diff-tree --stat --cc -p -M 3f69d405\n\nshows us that builtin-apply.c had a few insertion and deletion\n(remember, this is the diff between the first parent and the\nchild -- \"damage given to the first parent due to this merge\").\n\nIf you run the above without -M, you will see a huge combined\ndiff for builtin-apply.c because the second parent (i.e. the\nbranch that was merged) did not have builtin-apply.c -- it still\nhad the file under its old name, apply.c.\n\nSo what ends up getting combined without -M is diff between\nbuiltin-apply.c of the first parent and the child, and diff\nbetween /dev/null and builtin-apply.c of the child.\n\nBut with -M, what it combines is diff between builtin-apply.c of\nthe first parent and the child, and diff between apply.c of the\nsecond parent and builtin-apply.c of the child.  This obviously\nproduces a lot more reasonable output -- actually the merge for\nthis particular path is texually trivial that it does not even\nshow in the --cc output.  You can still view what it was by\nlooking at:\n\n\tgit diff-tree -c -p -M 3f69d405\n\nSide note: I am not sure if the --cc hunk droppage logic is\ndoing the right thing for the first hunk for builtin-apply.c\ncase.  I think it is an \"interesting\" hunk but somehow --cc\noutput does not show it.  The second and later hunks are\ndefinitely uninteresting, though.\n\n"}]}