{"thread":{"id":"15081","subject":"What's cooking in git.git (Aug 2008, #05; Tue, 19)","startedAt":"2008-08-19T09:05:42Z","lastAt":"2008-09-02T20:43:30Z","messageCount":20,"participants":["Junio C Hamano","Johannes Sixt","Andreas Färber","Miklos Vajna"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"87670","messageId":"7vpro5cr2x.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":null,"subject":"What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-19T09:05:42Z","receivedAt":"2008-08-19T09:05:42Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Here are the topics that have been cooking.  Commits prefixed\nwith '-' are only in 'pu' while commits prefixed with '+' are\nin 'next'.\n\nThe topics list the commits in reverse chronological order.  The topics\nmeant to be merged to the maintenance series have \"maint-\" in their names.\n\nTonight's 'pu' does not pass tests because test vectors have not been\nadjusted for the changes brought in by the jc/diff-prefix topic.\n\n----------------------------------------------------------------\n[New Topics]\n\n* js/mingw-stat (Mon Aug 18 22:01:06 2008 +0200) 2 commits\n - Revert \"Windows: Use a customized struct stat that also has the\n   st_blocks member.\"\n - compat: introduce on_disk_bytes()\n\nThis gets rid of use of st_blocks member (which is XSI but not POSIX\nproper), which was originally prompted by recent Haiku port but it turns\nout MinGW has the same issue as well.  Queued on 'pu' just to have a\nchance to make sure I munged the version j6t sent me correctly before\nmerging it upwards.\n\n* ml/submodule-foreach (Sun Aug 10 19:10:04 2008 -0400) 1 commit\n + git-submodule - Add 'foreach' subcommand\n\n* jc/stripspace (Sun Mar 9 00:30:35 2008 -0800) 6 commits\n - git-am --forge: add Signed-off-by: line for the author\n - git-am: clean-up Signed-off-by: lines\n - stripspace: add --log-clean option to clean up signed-off-by:\n   lines\n - stripspace: use parse_options()\n - Add \"git am -s\" test\n - git-am: refactor code to add signed-off-by line for the committer\n\n* pm/log-exit-code (Mon Aug 11 08:46:25 2008 +0200) 2 commits\n + Teach git log --exit-code to return an appropriate exit code\n + Teach git log --check to return an appropriate exit code\n\n* sb/commit-tree-minileak (Tue Aug 12 00:35:11 2008 +0200) 1 commit\n + Fix commit_tree() buffer leak\n\n* pb/reflog-dwim (Sun Aug 10 22:22:21 2008 +0200) 1 commit\n + builtin-reflog: Allow reflog expire to name partial ref\n\n* jc/send-pack-tell-me-more (Thu Mar 20 00:44:11 2008 -0700) 1 commit\n - \"git push\": tellme-more protocol extension\n\n* jc/merge-whitespace (Sun Feb 24 23:29:36 2008 -0800) 1 commit\n - WIP: start teaching the --whitespace=fix to merge machinery\n\n* lw/gitweb (Mon Aug 18 21:39:49 2008 +0200) 3 commits\n - gitweb: use new Git::Repo API, and add optional caching\n - add new Perl API: Git::Repo, Git::Commit, Git::Tag, and\n   Git::RepoRoot\n - gitweb: add test suite with Test::WWW::Mechanize::CGI\n\n* jc/diff-prefix (Mon Aug 18 20:08:09 2008 -0700) 1 commit\n - diff: vary default prefix depending on what are compared\n\n* jc/blame (Wed Jun 4 22:58:40 2008 -0700) 2 commits\n - blame: show \"previous\" information in --porcelain/--incremental\n   format\n - git-blame: refactor code to emit \"porcelain format\" output\n\n----------------------------------------------------------------\n[Graduated to \"master\"]\n\n* ak/p4 (Thu Aug 14 23:40:39 2008 +0100) 14 commits\n + Utilise our new p4_read_pipe and p4_write_pipe wrappers\n + Add p4 read_pipe and write_pipe wrappers\n + Put in the two other configuration elements found in the source\n + Put some documentation in about the parameters that have been\n   added\n + Move git-p4.syncFromOrigin into a configuration parameters section\n + Consistently use 'git-p4' for the configuration entries\n + If the user has configured various parameters, use them.\n + Switch to using 'p4_build_cmd'\n + If we are in verbose mode, output what we are about to run (or\n   return)\n + Add a single command that will be used to construct the 'p4'\n   command\n + Utilise the new 'p4_system' function.\n + Have a command that specifically invokes 'p4' (via system)\n + Utilise the new 'p4_read_pipe_lines' command\n + Create a specific version of the read_pipe_lines command for p4\n   invocations\n\nWarmly received by the primary contributors of git-p4; this was merged as\npart of 1.6.0.\n\n----------------------------------------------------------------\n[Will merge to master soon]\n\n* js/checkout-dwim-local (Sat Aug 9 16:00:12 2008 +0200) 1 commit\n + checkout --track: make up a sensible branch name if '-b' was\n   omitted\n\n* bd/diff-strbuf (Wed Aug 13 23:18:22 2008 -0700) 3 commits\n + xdiff-interface: hide the whole \"xdiff_emit_state\" business from\n   the caller\n + Use strbuf for struct xdiff_emit_state's remainder\n + Make xdi_diff_outf interface for running xdiff_outf diffs\n\nGives measurable performance improvement to textual diff generation.  For\nimproving \"blame\" performance, it might be more effective to hook directly\nto lower level of xdiff machinery so that we do not even have to generate\npatch only to discard after reading \"@@ -l,k +m,n @@\" lines, but that\nwould be a separate topic.\n\n* jc/add-stop-at-symlink (Mon Aug 4 00:52:37 2008 -0700) 2 commits\n + add: refuse to add working tree items beyond symlinks\n + update-index: refuse to add working tree items beyond symlinks\n\nFix for a longstanding bug that allows \"git add\" and \"git update-index\" to\nadd a path \"a/b\" to the index when \"a\" is a symbolic link.  We would need\na similar fix for the case where \"a\" is a submodule.\n\n* dp/hash-literally (Sun Aug 3 18:36:22 2008 +0400) 6 commits\n + add --no-filters option to git hash-object\n + add --path option to git hash-object\n + use parse_options() in git hash-object\n + correct usage help string for git-hash-object\n + correct argument checking test for git hash-object\n + teach index_fd to work with pipes\n\nGives a bit more flexibility to hash-objects by allowing us to lie about\nthe path the contents comes from.\n\n* mv/merge-custom (Wed Aug 13 23:32:43 2008 +0200) 7 commits\n + Update .gitignore to ignore git-help\n + Builtin git-help.\n + builtin-help: always load_command_list() in cmd_help()\n + Add a second testcase for handling invalid strategies in git-merge\n + Add a new test for using a custom merge strategy\n + builtin-merge: allow using a custom strategy\n + builtin-help: make some internal functions available to other\n   builtins\n\n* kh/diff-tree (Sun Aug 10 18:13:04 2008 +0200) 4 commits\n + Add test for diff-tree --stdin with two trees\n + Teach git diff-tree --stdin to diff trees\n + diff-tree: Note that the commit ID is printed with --stdin\n + Refactoring: Split up diff_tree_stdin\n\n* mg/count-objects (Fri Aug 15 00:20:20 2008 -0400) 1 commit\n + count-objects: Add total pack size to verbose output\n\nThis one is without the human readable bits.\n\n* mz/push-verbose (Sat Aug 16 19:58:32 2008 +0200) 1 commit\n + Make push more verbose about illegal combination of options\n\n* jc/index-extended-flags (Sat Aug 16 23:02:08 2008 -0700) 1 commit\n + index: future proof for \"extended\" index entries\n\n* cc/merge-base-many (Sun Jul 27 13:47:22 2008 -0700) 4 commits\n + git-merge-octopus: use (merge-base A (merge B C D E...)) for\n   stepwise merge\n + merge-base-many: add trivial tests based on the documentation\n + documentation: merge-base: explain \"git merge-base\" with more than\n   2 args\n + merge-base: teach \"git merge-base\" to drive underlying\n   merge_bases_many()\n\n* rs/imap (Wed Jul 9 22:29:02 2008 +0100) 5 commits\n + Documentation: Improve documentation for git-imap-send(1)\n + imap-send.c: more style fixes\n + imap-send.c: style fixes\n + git-imap-send: Support SSL\n + git-imap-send: Allow the program to be run from subdirectories of\n   a git tree\n\nSome people seem to prefer having this feature available also with gnutls.\nSuch an enhancement can be done in-tree on top of this series if they are\nso inclined.\n\n* jc/add-addremove (Tue Jul 22 22:30:40 2008 -0700) 2 commits\n + builtin-add.c: optimize -A option and \"git add .\"\n + builtin-add.c: restructure the code for maintainability\n\n* jk/pager-swap (Tue Jul 22 03:14:12 2008 -0400) 2 commits\n + spawn pager via run_command interface\n + run-command: add pre-exec callback\n\nThis changes the parent-child relationship between the pager and the git\nprocess.  We used to make pager the parent which meant that the exit\nstatus from git is lost from the caller.\n\n* ph/enable-threaded (Mon Jul 21 11:23:43 2008 +0200) 1 commit\n + Enable threaded delta search on *BSD and Linux.\n\n* am/cherry-pick-rerere (Sun Aug 10 17:18:55 2008 +0530) 1 commit\n + Make cherry-pick use rerere for conflict resolution.\n\n* js/parallel-test (Mon Aug 18 12:25:40 2008 -0400) 4 commits\n + Update t/.gitignore to ignore all trash directories\n + Enable parallel tests\n + tests: Clarify dependencies between tests, 'aggregate-results' and\n   'clean'\n + t9700: remove useless check\n\n* jc/test-deeper (Fri Aug 8 02:26:28 2008 -0700) 1 commit\n + tests: use $TEST_DIRECTORY to refer to the t/ directory\n\nThis does not actually move \"t/test directory\" any deeper, but fixes test\nscripts that assume they run immediately below \"t/\" to use TEST_DIRECTORY\nvariable.\n\n----------------------------------------------------------------\n[Actively Cooking]\n\n* sp/missing-thin-base (Tue Aug 12 11:31:06 2008 -0700) 1 commit\n + pack-objects: Allow missing base objects when creating thin packs\n\n* tr/filter-branch (Tue Aug 12 10:45:59 2008 +0200) 3 commits\n + filter-branch: use --simplify-merges\n + filter-branch: fix ref rewriting with --subdirectory-filter\n + filter-branch: Extend test to show rewriting bug\n\nFixes a longstanding filter branch bug.\n\n* jc/post-simplify (Fri Aug 15 01:34:51 2008 -0700) 8 commits\n - revision --simplify-merges: incremental simplification\n - revision --simplify-merges: prepare for incremental simplification\n - revision --simplify-merges: make it a no-op without pathspec\n + revision --simplify-merges: do not leave commits unprocessed\n + revision --simplify-merges: use decoration instead of commit->util\n   field\n + Topo-sort before --simplify-merges\n + revision traversal: show full history with merge simplification\n + revision.c: whitespace fix\n\n\"log --full-history\" is with too much clutter, \"log\" itself is too cleverer\nthan some people, and here is the middle level of merge simplification.\n\nI started making this incremental but the progress is not so great.\n\n* tr/rev-list-docs (Tue Aug 12 01:55:37 2008 +0200) 1 commit\n + Documentation: rev-list-options: move --simplify-merges\n   documentation\n\n----------------------------------------------------------------\n[On Hold]\n\n* lt/time-reject-fractional-seconds (Sat Aug 16 21:25:40 2008 -0700) 1 commit\n - date/time: do not get confused by fractional seconds\n\nLinus hints further enhancements as \"the right way\", so let's see if\nsomebody else steps up and tries it before merging this to 'next'.\n\n* jc/cc-ld-dynpath (Sat Aug 16 15:01:23 2008 +0200) 2 commits\n - configure: auto detect dynamic library path switches\n - Makefile: Allow CC_LD_DYNPATH to be overriden\n\nNeeds success reports from people who do use user-defined dynamic library\npath when they build their \"git\" before this series can go anywhere.\n\n* sb/daemon (Thu Aug 14 20:02:20 2008 +0200) 4 commits\n - git-daemon: rewrite kindergarden, new option --max-connections\n - git-daemon: Simplify dead-children reaping logic\n - git-daemon: use LOG_PID, simplify logging code\n - git-daemon: call logerror() instead of error()\n\nCan somebody who actually runs the daemon standalone comment on this one?\n\n* mv/merge-recursive (Tue Aug 12 22:14:00 2008 +0200) 3 commits\n - Make builtin-revert.c use merge_recursive_generic()\n - merge-recursive.c: Add more generic merge_recursive_generic()\n - Split out merge_recursive() to merge-recursive.c\n\nI do not think builtlin-revert should use \"recursive\", but these patches\ngive a good starting point to separate the bulk of the \"rename-aware\nthree-way merge\" into library form.\n\n* sp/smart-http (Sun Aug 3 00:25:17 2008 -0700) 2 commits\n - [do not merge -- original version] Add Git-aware CGI for Git-aware\n   smart HTTP transport\n - Add backdoor options to receive-pack for use in Git-aware CGI\n\nThe \"magic\" detection protocol was revised to use POST to info/refs; the\ntop one queued is from before that discussion.\n\n* cc/bisect (Fri Jul 25 05:36:37 2008 +0200) 2 commits\n - bisect: only check merge bases when needed\n - bisect: test merge base if good rev is not an ancestor of bad rev\n\nThe first one alone does not pass its self-test but combined together they\nseem to.  It does not build confidence as the latter one is supposed to be\nan optimization only.  Resend of fixed-up series is needed.\n\n* sg/merge-options (Sun Apr 6 03:23:47 2008 +0200) 1 commit\n + merge: remove deprecated summary and diffstat options and config\n   variables\n\nThis was previously in \"will be in master soon\" category, but it turns out\nthat the synonyms to the ones this one deletes are fairly new invention\nthat happend in 1.5.6 timeframe, and we cannot do this just yet.  Perhaps\nin 1.7.0.\n\n* jc/dashless (Wed Jun 25 15:55:11 2008 -0700) 1 commit\n - Make clients ask for \"git program\" over ssh and local transport\n\nThis is the \"botched\" one.  Will be resurrected during 1.7.0 or 1.8.0\ntimeframe.\n\n* jk/renamelimit (Sat May 3 13:58:42 2008 -0700) 1 commit\n - diff: enable \"too large a rename\" warning when -M/-C is explicitly\n   asked for\n\nThis would be the right thing to do for command line use, but gitk will be\nhit due to tcl/tk's limitation, so I am holding this back for now.\n"},{"id":"87678","messageId":"48AAA82F.9060500@viscovery.net","threadId":"15081","inReplyTo":"7vpro5cr2x.fsf@gitster.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-08-19T11:02:07Z","receivedAt":"2008-08-19T11:02:07Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> * js/mingw-stat (Mon Aug 18 22:01:06 2008 +0200) 2 commits\n>  - Revert \"Windows: Use a customized struct stat that also has the\n>    st_blocks member.\"\n>  - compat: introduce on_disk_bytes()\n> \n> This gets rid of use of st_blocks member (which is XSI but not POSIX\n> proper), which was originally prompted by recent Haiku port but it turns\n> out MinGW has the same issue as well.  Queued on 'pu' just to have a\n> chance to make sure I munged the version j6t sent me correctly before\n> merging it upwards.\n\nI tested this again, and it works as expected.\n\n-- Hannes\n"},{"id":"87684","messageId":"6E32922B-EBBD-4317-8E98-DF3CE6EE0E39@web.de","threadId":"15081","inReplyTo":"48AAA82F.9060500@viscovery.net","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Andreas Färber","fromEmail":"andreas.faerber@web.de","sentAt":"2008-08-19T12:35:31Z","receivedAt":"2008-08-19T12:35:31Z","isPatch":false,"sender":{"key":"andreas.faerber@web.de","avatar":"https://gravatar.com/avatar/ac719235a76c4a5e248080fee1f7b6f507bdb1f3d71f7ae48457f3ead91ed1c0?d=mp&s=160"},"body":"\nAm 19.08.2008 um 13:02 schrieb Johannes Sixt:\n\n> Junio C Hamano schrieb:\n>> * js/mingw-stat (Mon Aug 18 22:01:06 2008 +0200) 2 commits\n>> - Revert \"Windows: Use a customized struct stat that also has the\n>>   st_blocks member.\"\n>> - compat: introduce on_disk_bytes()\n>>\n>> This gets rid of use of st_blocks member (which is XSI but not POSIX\n>> proper), which was originally prompted by recent Haiku port but it  \n>> turns\n>> out MinGW has the same issue as well.  Queued on 'pu' just to have a\n>> chance to make sure I munged the version j6t sent me correctly before\n>> merging it upwards.\n>\n> I tested this again, and it works as expected.\n\nSo did I for the latter, on Haiku. Together with the hardlink patch  \nnot yet queued, this allows to build and install (*) via `make` with  \nsuitable arguments.\n\nAndreas\n\n\n(*) Haiku's `read` (bash) seems to be broken - using `ls -1` in  \ntemplates/Makefile works around that, but it should be fixed at the  \nsource. http://dev.haiku-os.org/ticket/2646\n"},{"id":"87685","messageId":"20080819125429.GD17582@genesis.frugalware.org","threadId":"15081","inReplyTo":"7vpro5cr2x.fsf@gitster.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-19T12:54:29Z","receivedAt":"2008-08-19T12:54:29Z","isPatch":false,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Tue, Aug 19, 2008 at 02:05:42AM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> [On Hold]\n> (...)\n> * mv/merge-recursive (Tue Aug 12 22:14:00 2008 +0200) 3 commits\n>  - Make builtin-revert.c use merge_recursive_generic()\n>  - merge-recursive.c: Add more generic merge_recursive_generic()\n>  - Split out merge_recursive() to merge-recursive.c\n> \n> I do not think builtlin-revert should use \"recursive\", but these patches\n> give a good starting point to separate the bulk of the \"rename-aware\n> three-way merge\" into library form.\n\nI wanted to send a patch that makes builtin-merge use the new\nmerge_recursive_setup(), but then I was not able to decide to use\nmerge_recursive_generic() or not.\n\nWhat is your preference here? I just want to avoid a \"this could be\nmerged, but it uses merge_recursive(), not merge_recursive_generic()\" or\nthe opposite of this. :)\n\nThanks.\n"},{"id":"87741","messageId":"7vabf83j9u.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"20080819125429.GD17582@genesis.frugalware.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-19T19:19:09Z","receivedAt":"2008-08-19T19:19:09Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> On Tue, Aug 19, 2008 at 02:05:42AM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n>> [On Hold]\n>> (...)\n>> * mv/merge-recursive (Tue Aug 12 22:14:00 2008 +0200) 3 commits\n>>  - Make builtin-revert.c use merge_recursive_generic()\n>>  - merge-recursive.c: Add more generic merge_recursive_generic()\n>>  - Split out merge_recursive() to merge-recursive.c\n>> \n>> I do not think builtlin-revert should use \"recursive\", but these patches\n>> give a good starting point to separate the bulk of the \"rename-aware\n>> three-way merge\" into library form.\n>\n> I wanted to send a patch that makes builtin-merge use the new\n> merge_recursive_setup(), but then I was not able to decide to use\n> merge_recursive_generic() or not.\n\nI think git-merge and git-merge-recursive should be the only two that\nactually trigger the \"recursive\" behaviour.  Everybody else should be\nusing non-recursive one, and that non-recursive one can be shared by the\none that is recursive.\n\nHere is how the callchain looks like with your variant.\n\n cmd_merge_recursive()\n -> merge_recursive_setup()\n -> merge_recursive_generic()\n    -> merge_recursive()\n       -> merge_recursive()\n       -> merge_trees()\n\nThe merge_recursive() is the \"recursive\" one.  The workhorse that is not\nrecursive is merge_trees().\n\nSince the latter is what everybody else (\"checkout -m\", \"revert\",\n\"cherry-pick\", \"am -3\", \"stash apply\") should be using, I think it is\npretty much up to \"git-merge\" and \"git-merge-recursive\" implementations\nhow the caller of merge_recursive() function is structured.  I suspect\nthat you would not need two separate functions, _setup() and _generic(),\nfor these two codepaths, but I didn't look closely.\n\nAnd make_virtual_commit() should become static inside merge_recursive.c;\nuse of these fake commits is strictly an internal implementation issue of\nhow merge_recursive() function works and does not concern the caller, does\nit?\n\nBy the way, the calling convention of merge_recursive_generic() looks\nconfusing (even though by the above reasoning it does not matter very much\noutside \"git-merge\" and \"git-merge-recursive\").  Why does it take textual\nobject names for bases but binary object names for head and next?\n"},{"id":"87754","messageId":"20080819205917.GJ23800@genesis.frugalware.org","threadId":"15081","inReplyTo":"7vabf83j9u.fsf@gitster.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-19T20:59:17Z","receivedAt":"2008-08-19T20:59:17Z","isPatch":false,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Tue, Aug 19, 2008 at 12:19:09PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> Since the latter is what everybody else (\"checkout -m\", \"revert\",\n> \"cherry-pick\", \"am -3\", \"stash apply\") should be using, I think it is\n> pretty much up to \"git-merge\" and \"git-merge-recursive\" implementations\n> how the caller of merge_recursive() function is structured.  I suspect\n> that you would not need two separate functions, _setup() and _generic(),\n> for these two codepaths, but I didn't look closely.\n\nSure, I can avoid _generic() and use merge_recursive() directly, that's\nwhy I asked.\n\n> And make_virtual_commit() should become static inside merge_recursive.c;\n> use of these fake commits is strictly an internal implementation issue of\n> how merge_recursive() function works and does not concern the caller, does\n> it?\n\nNot exactly. builtin-merge-recursive uses get_ref() - which should not\nbe in merge-recursive.c IMHO - and get_ref() uses make_virtual_commit().\nmerge_recursive() itself takes commits, so it can be only static if we\ncopy it builtin-merge-recursive as well, causing a code duplication. Or\nhave I missed something here?\n\nThanks.\n"},{"id":"87759","messageId":"7v3al0zmv8.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"20080819205917.GJ23800@genesis.frugalware.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-19T22:00:27Z","receivedAt":"2008-08-19T22:00:27Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> On Tue, Aug 19, 2008 at 12:19:09PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n>> Since the latter is what everybody else (\"checkout -m\", \"revert\",\n>> \"cherry-pick\", \"am -3\", \"stash apply\") should be using, I think it is\n>> pretty much up to \"git-merge\" and \"git-merge-recursive\" implementations\n>> how the caller of merge_recursive() function is structured.  I suspect\n>> that you would not need two separate functions, _setup() and _generic(),\n>> for these two codepaths, but I didn't look closely.\n>\n> Sure, I can avoid _generic() and use merge_recursive() directly, that's\n> why I asked.\n>\n>> And make_virtual_commit() should become static inside merge_recursive.c;\n>> use of these fake commits is strictly an internal implementation issue of\n>> how merge_recursive() function works and does not concern the caller, does\n>> it?\n>\n> Not exactly. builtin-merge-recursive uses get_ref() - which should not\n> be in merge-recursive.c IMHO - and get_ref() uses make_virtual_commit().\n> merge_recursive() itself takes commits, so it can be only static if we\n> copy it builtin-merge-recursive as well, causing a code duplication. Or\n> have I missed something here?\n\nI think you have.\n\nLet's look at the call chain from cmd_merge_recursive() and think again.\n\n    cmd_merge_recursive()\n    -> merge_recursive_setup()\n    -> merge_recursive_generic()\n       -> merge_recursive()\n          -> merge_recursive()\n          -> merge_trees()\n\ncmd_merge_recursive() takes subtree option and set of object names (two\ncommits and zero or more base commits), massages them and calls\nmerge_recursive().  merge_recursive_setup() and merge_recursive_generic()\nare involved in this massaging process.\n\nmerge_recursive() computes the bases itself when given no base, and in a\nmulti-base situation, does its thing recursively to come up with a\nconsolidated base, using virtual commits.  After coming up with the three\n(virtual or real) commits to use, it gives them to merge_trees(), which\noperate solely on tree objects.\n\nIn addition, merge_recursive() currently *requires* the caller to wrap\nbare tree objects in virtual commits, if the caller wants to do a simple\nthree-way merge of trees (in which case because there is no ancestry\ninformation available you would naturally not do any recursive behaviour).\nThis \"input must be commit\" requirement is why you think you need to have\nget_ref() that uses make_virtual_commit() in the caller.\n\nBut it does not have to be that way.  It is merely an artifact of the\ncurrent refactoring that kept the interface into merge_recursive() based\non commit objects.  You could further refine the refactoring so that:\n\n - merge_trees(), in addition to the three tree objects, takes options\n   such as use of the subtree behaviour, descriptive names for heads to be\n   used for conflict markers, verbosity level, and other future options\n   (such as \"use this lower rename detaction threshold\").  Introduce\n   \"struct merge_options\" for that and pass it around.  These show() and\n   output() calls could even become callbacks, but I didn't look very\n   carefully.\n\n - merge_recursive(), in addition to that \"merge_options\" structure, will\n   take heads, and list of common ancestors.\n\n - merge_recursive_generic() can be a layer on top of merge_recursive() to\n   allow the caller to feed tree objects.  Use of \"const unsigned char *\"\n   to give raw object names (or even \"const char *\" to feed texual object\n   names) would be easier for the callers.  Wrapping a tree into virtual\n   commit can and should be done at this layer, hidden away inside\n   merge-recursive.c from the callers.\n\n   Alternatively, you can do away without such preparation step, and move\n   the \"wrap a tree into a virtual commit\" inside merge_recursive()\n   itself.  If you take that route, merge_recursive() will take heads and\n   list of common ancestors all in \"const unsigned char *\" object names,\n   in addition to the \"merge_options\" structure.\n\nWhen you rewrite cmd_merge() to make direct call to bypass a subprocess,\nyour callchain would look like:\n\n    cmd_merge()\n    -> merge_recursive()\n       -> merge_recursive()\n       -> merge_trees()\n\ncmd_merge() needs to do the same arrangement for \"subtree\" and any\npossible future options, and feed the same set of object names to\nmerge_recursive().  You cannot give a bare tree to \"git merge\", so you do\nnot have to worry about having to wrap it in a virtual commit.\n\nSo my gut feeling is that the interface may look something like:\n\nstruct merge_options {\n        const char *branch1_label;\n        const char *branch2_label;\n        unsigned subtree_merge : 1;\n\tint verbosity;\n        /* other options here ... */\n};\n\n/* rename-detecting three-way merge, no recursion */\nint merge_trees(struct merge_options *,\n                struct tree *head,\n                struct tree *merge,\n                struct tree *common,\n                struct tree **result);\n\n/* merge_trees() but with recursive ancestor consolidation */\nint merge_recursive(struct merge_options *,\n                    struct commit *h1,\n                    struct commit *h2,\n                    struct commit_list *ca,\n                    struct commit **result);\n\n/*\n * \"git-merge-recursive\" can be fed trees; wrap them into\n * virtual commits and call merge_recursive() proper.\n */\nint merge_recursive_generic(struct merge_options *,\n                            const unsigned char *head,\n                            const unsigned char *merge,\n                            int num_ca,\n                            const unsigned char **ca,\n                            struct commit **result);\n\nand the call chain would become:\n\n    cmd_merge_recursive()\n    -> merge_recursive_generic()\n       -> merge_recursive()\n          -> merge_recursive()\n          -> merge_trees()\n\n    cmd_merge()\n    -> merge_recursive()\n       -> merge_recursive()\n       -> merge_trees()\n\n    cmd_revert(), cmd_am(), cmd_checkout(), cmd_stash(), ...\n    -> merge_trees()\n"},{"id":"87901","messageId":"20080820224229.GO23800@genesis.frugalware.org","threadId":"15081","inReplyTo":"7v3al0zmv8.fsf@gitster.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-20T22:42:29Z","receivedAt":"2008-08-20T22:42:29Z","isPatch":false,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Tue, Aug 19, 2008 at 03:00:27PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> > Not exactly. builtin-merge-recursive uses get_ref() - which should not\n> > be in merge-recursive.c IMHO - and get_ref() uses make_virtual_commit().\n> > merge_recursive() itself takes commits, so it can be only static if we\n> > copy it builtin-merge-recursive as well, causing a code duplication. Or\n> > have I missed something here?\n> \n> I think you have.\n> \n> Let's look at the call chain from cmd_merge_recursive() and think again.\n\nThanks for the detailed answer. I just wanted to say that probably I\nwon't have time to implement this before the weekend; but I plan to do\nso then.\n"},{"id":"88415","messageId":"1219628677-23903-1-git-send-email-vmiklos@frugalware.org","threadId":"15081","inReplyTo":"7v3al0zmv8.fsf@gitster.siamese.dyndns.org","subject":"[PATCH] merge-recursive: introduce merge_options","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-25T01:44:37Z","receivedAt":"2008-08-25T01:44:37Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"This makes it possible to avoid passing the labels of branches as\narguments to merge_recursive(), merge_trees() and\nmerge_recursive_generic().\n\nIt also takes care of subtree merge, output buffering, verbosity, and\nrename limits - these were global variables till now in\nmerge-recursive.c.\n\nA new function, named init_merge_options(), is introduced as well, it\nclears the struct merge_info, then initializes with default values,\nfinally updates the default values based on the config and environment\nvariables.\n\nSigned-off-by: Miklos Vajna <vmiklos@frugalware.org>\n---\n\nOn Tue, Aug 19, 2008 at 03:00:27PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> and the call chain would become:\n>\n>     cmd_merge_recursive()\n>     -> merge_recursive_generic()\n>        -> merge_recursive()\n>           -> merge_recursive()\n>           -> merge_trees()\n\nActually I still left some extra function, as the merge_options\nstructure should be initialized, and that was done by just initializing\nglobal variables before, but now we have to have a new function for it.\n\nOther than that, I hope the patch looks like the way you imagined. ;-)\n\nNotes:\n\n1) This applies on top of 1c868d4 (merge-recursive.c: Add more generic\nmerge_recursive_generic()). I can rebase this (along with 1c868d4 and\n1c868d4^) on top of current master, if this is a problem.\n\n2) I know that this patch is huge, but we want to have the verbosity\nflag in merge_options, so it has to be passed as an argument in many\nplaces.\n\n>     cmd_merge()\n>     -> merge_recursive()\n>        -> merge_recursive()\n>        -> merge_trees()\n>\n>     cmd_revert(), cmd_am(), cmd_checkout(), cmd_stash(), ...\n>     -> merge_trees()\n\nbuiltin-checkout already used merge_trees(), so I modified it to use\nmerge_options, otherwise the patch would break the build. 'am' and\n'stash' is not (yet) a builtin, so that is not interesting here. If this\npatch looks OK, then I want to do the builtin-merge and builtin-revert\nparts as well.\n\n builtin-checkout.c        |   11 ++-\n builtin-merge-recursive.c |   43 ++++----\n merge-recursive.c         |  242 ++++++++++++++++++++++----------------------\n merge-recursive.h         |   42 ++++++---\n 4 files changed, 180 insertions(+), 158 deletions(-)\n\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 411cc51..3627996 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -264,6 +264,7 @@ static int merge_working_tree(struct checkout_opts *opts,\n \t\t\t */\n \t\t\tstruct tree *result;\n \t\t\tstruct tree *work;\n+\t\t\tstruct merge_options o;\n \t\t\tif (!opts->merge)\n \t\t\t\treturn 1;\n \t\t\tparse_commit(old->commit);\n@@ -282,13 +283,17 @@ static int merge_working_tree(struct checkout_opts *opts,\n \t\t\t */\n \n \t\t\tadd_files_to_cache(NULL, NULL, 0);\n-\t\t\twork = write_tree_from_memory();\n+\t\t\tinit_merge_options(&o);\n+\t\t\to.verbosity = 0;\n+\t\t\twork = write_tree_from_memory(&o);\n \n \t\t\tret = reset_tree(new->commit->tree, opts, 1);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\n-\t\t\tmerge_trees(new->commit->tree, work, old->commit->tree,\n-\t\t\t\t    new->name, \"local\", &result);\n+\t\t\to.branch1 = new->name;\n+\t\t\to.branch2 = \"local\";\n+\t\t\tmerge_trees(&o, new->commit->tree, work,\n+\t\t\t\told->commit->tree, &result);\n \t\t\tret = reset_tree(new->commit->tree, opts, 0);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\ndiff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c\nindex 25f540b..6b534c1 100644\n--- a/builtin-merge-recursive.c\n+++ b/builtin-merge-recursive.c\n@@ -17,32 +17,33 @@ static const char *better_branch_name(const char *branch)\n \n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *bases[21];\n+\tconst unsigned char *bases[21];\n \tunsigned bases_count = 0;\n \tint i, failed;\n-\tconst char *branch1, *branch2;\n \tunsigned char h1[20], h2[20];\n-\tint subtree_merge = 0;\n+\tstruct merge_options o;\n+\tstruct commit *result;\n \n+\tinit_merge_options(&o);\n \tif (argv[0]) {\n \t\tint namelen = strlen(argv[0]);\n \t\tif (8 < namelen &&\n \t\t    !strcmp(argv[0] + namelen - 8, \"-subtree\"))\n-\t\t\tsubtree_merge = 1;\n+\t\t\to.subtree_merge = 1;\n \t}\n \n-\tgit_config(merge_recursive_config, NULL);\n-\tmerge_recursive_setup(subtree_merge);\n \tif (argc < 4)\n \t\tdie(\"Usage: %s <base>... -- <head> <remote> ...\\n\", argv[0]);\n \n \tfor (i = 1; i < argc; ++i) {\n-\t\tif (!strcmp(argv[i], \"--\")) {\n-\t\t\tbases[bases_count] = NULL;\n+\t\tif (!strcmp(argv[i], \"--\"))\n \t\t\tbreak;\n+\t\tif (bases_count < ARRAY_SIZE(bases)-1) {\n+\t\t\tunsigned char *sha = xmalloc(20);\n+\t\t\tif (get_sha1(argv[i], sha))\n+\t\t\t\tdie(\"Could not parse object '%s'\", argv[i]);\n+\t\t\tbases[bases_count++] = sha;\n \t\t}\n-\t\tif (bases_count < ARRAY_SIZE(bases)-1)\n-\t\t\tbases[bases_count++] = argv[i];\n \t\telse\n \t\t\twarning(\"Cannot handle more than %zu bases. \"\n \t\t\t\t\"Ignoring %s.\", ARRAY_SIZE(bases)-1, argv[i]);\n@@ -50,21 +51,21 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n \t\tdie(\"Not handling anything other than two heads merge.\");\n \n-\tbranch1 = argv[++i];\n-\tbranch2 = argv[++i];\n+\to.branch1 = argv[++i];\n+\to.branch2 = argv[++i];\n \n-\tif (get_sha1(branch1, h1))\n-\t\tdie(\"Could not resolve ref '%s'\", branch1);\n-\tif (get_sha1(branch2, h2))\n-\t\tdie(\"Could not resolve ref '%s'\", branch2);\n+\tif (get_sha1(o.branch1, h1))\n+\t\tdie(\"Could not resolve ref '%s'\", o.branch1);\n+\tif (get_sha1(o.branch2, h2))\n+\t\tdie(\"Could not resolve ref '%s'\", o.branch2);\n \n-\tbranch1 = better_branch_name(branch1);\n-\tbranch2 = better_branch_name(branch2);\n+\to.branch1 = better_branch_name(o.branch1);\n+\to.branch2 = better_branch_name(o.branch2);\n \n-\tif (merge_recursive_verbosity >= 3)\n-\t\tprintf(\"Merging %s with %s\\n\", branch1, branch2);\n+\tif (o.verbosity >= 3)\n+\t\tprintf(\"Merging %s with %s\\n\", o.branch1, o.branch2);\n \n-\tfailed = merge_recursive_generic(bases, h1, branch1, h2, branch2);\n+\tfailed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);\n \tif (failed < 0)\n \t\treturn 128; /* die() error code */\n \treturn failed;\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 74a9fdc..ee23396 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -83,16 +83,11 @@ static struct string_list current_file_set = {NULL, 0, 0, 1};\n static struct string_list current_directory_set = {NULL, 0, 0, 1};\n \n static int call_depth = 0;\n-int merge_recursive_verbosity = 2;\n-static int diff_rename_limit = -1;\n-static int merge_rename_limit = -1;\n-static int buffer_output = 1;\n static struct strbuf obuf = STRBUF_INIT;\n \n-static int show(int v)\n+static int show(int v, struct merge_options *o)\n {\n-\treturn (!call_depth && merge_recursive_verbosity >= v) ||\n-\t\tmerge_recursive_verbosity >= 5;\n+\treturn (!call_depth && o->verbosity >= v) || o->verbosity >= 5;\n }\n \n static void flush_output(void)\n@@ -103,12 +98,12 @@ static void flush_output(void)\n \t}\n }\n \n-static void output(int v, const char *fmt, ...)\n+static void output(int v, struct merge_options *o, const char *fmt, ...)\n {\n \tint len;\n \tva_list ap;\n \n-\tif (!show(v))\n+\tif (!show(v, o))\n \t\treturn;\n \n \tstrbuf_grow(&obuf, call_depth * 2 + 2);\n@@ -132,7 +127,7 @@ static void output(int v, const char *fmt, ...)\n \t}\n \tstrbuf_setlen(&obuf, obuf.len + len);\n \tstrbuf_add(&obuf, \"\\n\", 1);\n-\tif (!buffer_output)\n+\tif (!o->buffer_output)\n \t\tflush_output();\n }\n \n@@ -219,17 +214,17 @@ static int git_merge_trees(int index_only,\n \treturn rc;\n }\n \n-struct tree *write_tree_from_memory(void)\n+struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n \n \tif (unmerged_cache()) {\n \t\tint i;\n-\t\toutput(0, \"There are unmerged index entries:\");\n+\t\toutput(0, o, \"There are unmerged index entries:\");\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tif (ce_stage(ce))\n-\t\t\t\toutput(0, \"%d %.*s\", ce_stage(ce), ce_namelen(ce), ce->name);\n+\t\t\t\toutput(0, o, \"%d %.*s\", ce_stage(ce), ce_namelen(ce), ce->name);\n \t\t}\n \t\treturn NULL;\n \t}\n@@ -345,7 +340,8 @@ static struct string_list *get_renames(struct tree *tree,\n \t\t\t\t\tstruct tree *o_tree,\n \t\t\t\t\tstruct tree *a_tree,\n \t\t\t\t\tstruct tree *b_tree,\n-\t\t\t\t\tstruct string_list *entries)\n+\t\t\t\t\tstruct string_list *entries,\n+\t\t\t\t\tstruct merge_options *o)\n {\n \tint i;\n \tstruct string_list *renames;\n@@ -355,8 +351,8 @@ static struct string_list *get_renames(struct tree *tree,\n \tdiff_setup(&opts);\n \tDIFF_OPT_SET(&opts, RECURSIVE);\n \topts.detect_rename = DIFF_DETECT_RENAME;\n-\topts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit :\n-\t\t\t    diff_rename_limit >= 0 ? diff_rename_limit :\n+\topts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :\n+\t\t\t    o->diff_rename_limit >= 0 ? o->diff_rename_limit :\n \t\t\t    500;\n \topts.warn_on_too_large_rename = 1;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -720,7 +716,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o,\n static void conflict_rename_rename(struct rename *ren1,\n \t\t\t\t   const char *branch1,\n \t\t\t\t   struct rename *ren2,\n-\t\t\t\t   const char *branch2)\n+\t\t\t\t   const char *branch2,\n+\t\t\t\t   struct merge_options *o)\n {\n \tchar *del[2];\n \tint delp = 0;\n@@ -730,13 +727,13 @@ static void conflict_rename_rename(struct rename *ren1,\n \tconst char *dst_name2 = ren2_dst;\n \tif (string_list_has_string(&current_directory_set, ren1_dst)) {\n \t\tdst_name1 = del[delp++] = unique_path(ren1_dst, branch1);\n-\t\toutput(1, \"%s is a directory in %s added as %s instead\",\n+\t\toutput(1, o, \"%s is a directory in %s added as %s instead\",\n \t\t       ren1_dst, branch2, dst_name1);\n \t\tremove_file(0, ren1_dst, 0);\n \t}\n \tif (string_list_has_string(&current_directory_set, ren2_dst)) {\n \t\tdst_name2 = del[delp++] = unique_path(ren2_dst, branch2);\n-\t\toutput(1, \"%s is a directory in %s added as %s instead\",\n+\t\toutput(1, o, \"%s is a directory in %s added as %s instead\",\n \t\t       ren2_dst, branch1, dst_name2);\n \t\tremove_file(0, ren2_dst, 0);\n \t}\n@@ -758,10 +755,11 @@ static void conflict_rename_rename(struct rename *ren1,\n }\n \n static void conflict_rename_dir(struct rename *ren1,\n-\t\t\t\tconst char *branch1)\n+\t\t\t\tconst char *branch1,\n+\t\t\t\tstruct merge_options *o)\n {\n \tchar *new_path = unique_path(ren1->pair->two->path, branch1);\n-\toutput(1, \"Renamed %s to %s instead\", ren1->pair->one->path, new_path);\n+\toutput(1, o, \"Renamed %s to %s instead\", ren1->pair->one->path, new_path);\n \tremove_file(0, ren1->pair->two->path, 0);\n \tupdate_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);\n \tfree(new_path);\n@@ -770,11 +768,12 @@ static void conflict_rename_dir(struct rename *ren1,\n static void conflict_rename_rename_2(struct rename *ren1,\n \t\t\t\t     const char *branch1,\n \t\t\t\t     struct rename *ren2,\n-\t\t\t\t     const char *branch2)\n+\t\t\t\t     const char *branch2,\n+\t\t\t\t     struct merge_options *o)\n {\n \tchar *new_path1 = unique_path(ren1->pair->two->path, branch1);\n \tchar *new_path2 = unique_path(ren2->pair->two->path, branch2);\n-\toutput(1, \"Renamed %s to %s and %s to %s instead\",\n+\toutput(1, o, \"Renamed %s to %s and %s to %s instead\",\n \t       ren1->pair->one->path, new_path1,\n \t       ren2->pair->one->path, new_path2);\n \tremove_file(0, ren1->pair->two->path, 0);\n@@ -786,8 +785,7 @@ static void conflict_rename_rename_2(struct rename *ren1,\n \n static int process_renames(struct string_list *a_renames,\n \t\t\t   struct string_list *b_renames,\n-\t\t\t   const char *a_branch,\n-\t\t\t   const char *b_branch)\n+\t\t\t   struct merge_options *o)\n {\n \tint clean_merge = 1, i, j;\n \tstruct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};\n@@ -832,15 +830,15 @@ static int process_renames(struct string_list *a_renames,\n \t\t\trenames1 = a_renames;\n \t\t\trenames2 = b_renames;\n \t\t\trenames2Dst = &b_by_dst;\n-\t\t\tbranch1 = a_branch;\n-\t\t\tbranch2 = b_branch;\n+\t\t\tbranch1 = o->branch1;\n+\t\t\tbranch2 = o->branch2;\n \t\t} else {\n \t\t\tstruct rename *tmp;\n \t\t\trenames1 = b_renames;\n \t\t\trenames2 = a_renames;\n \t\t\trenames2Dst = &a_by_dst;\n-\t\t\tbranch1 = b_branch;\n-\t\t\tbranch2 = a_branch;\n+\t\t\tbranch1 = o->branch2;\n+\t\t\tbranch2 = o->branch1;\n \t\t\ttmp = ren2;\n \t\t\tren2 = ren1;\n \t\t\tren1 = tmp;\n@@ -867,7 +865,7 @@ static int process_renames(struct string_list *a_renames,\n \t\t\tren2->processed = 1;\n \t\t\tif (strcmp(ren1_dst, ren2_dst) != 0) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(1, \"CONFLICT (rename/rename): \"\n+\t\t\t\toutput(1, o, \"CONFLICT (rename/rename): \"\n \t\t\t\t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n \t\t\t\t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\",\n \t\t\t\t       src, ren1_dst, branch1,\n@@ -878,7 +876,7 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\tupdate_file(0, ren1->pair->one->sha1,\n \t\t\t\t\t\t    ren1->pair->one->mode, src);\n \t\t\t\t}\n-\t\t\t\tconflict_rename_rename(ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename(ren1, branch1, ren2, branch2, o);\n \t\t\t} else {\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tremove_file(1, ren1_src, 1);\n@@ -888,13 +886,13 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\t\t branch1,\n \t\t\t\t\t\t branch2);\n \t\t\t\tif (mfi.merge || !mfi.clean)\n-\t\t\t\t\toutput(1, \"Renamed %s->%s\", src, ren1_dst);\n+\t\t\t\t\toutput(1, o, \"Renamed %s->%s\", src, ren1_dst);\n \n \t\t\t\tif (mfi.merge)\n-\t\t\t\t\toutput(2, \"Auto-merged %s\", ren1_dst);\n+\t\t\t\t\toutput(2, o, \"Auto-merged %s\", ren1_dst);\n \n \t\t\t\tif (!mfi.clean) {\n-\t\t\t\t\toutput(1, \"CONFLICT (content): merge conflict in %s\",\n+\t\t\t\t\toutput(1, o, \"CONFLICT (content): merge conflict in %s\",\n \t\t\t\t\t       ren1_dst);\n \t\t\t\t\tclean_merge = 0;\n \n@@ -925,14 +923,14 @@ static int process_renames(struct string_list *a_renames,\n \n \t\t\tif (string_list_has_string(&current_directory_set, ren1_dst)) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(1, \"CONFLICT (rename/directory): Renamed %s->%s in %s \"\n+\t\t\t\toutput(1, o, \"CONFLICT (rename/directory): Renamed %s->%s in %s \"\n \t\t\t\t       \" directory %s added in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren1_dst, branch2);\n-\t\t\t\tconflict_rename_dir(ren1, branch1);\n+\t\t\t\tconflict_rename_dir(ren1, branch1, o);\n \t\t\t} else if (sha_eq(src_other.sha1, null_sha1)) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(1, \"CONFLICT (rename/delete): Renamed %s->%s in %s \"\n+\t\t\t\toutput(1, o, \"CONFLICT (rename/delete): Renamed %s->%s in %s \"\n \t\t\t\t       \"and deleted in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       branch2);\n@@ -941,31 +939,31 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\tconst char *new_path;\n \t\t\t\tclean_merge = 0;\n \t\t\t\ttry_merge = 1;\n-\t\t\t\toutput(1, \"CONFLICT (rename/add): Renamed %s->%s in %s. \"\n+\t\t\t\toutput(1, o, \"CONFLICT (rename/add): Renamed %s->%s in %s. \"\n \t\t\t\t       \"%s added in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren1_dst, branch2);\n \t\t\t\tnew_path = unique_path(ren1_dst, branch2);\n-\t\t\t\toutput(1, \"Added as %s instead\", new_path);\n+\t\t\t\toutput(1, o, \"Added as %s instead\", new_path);\n \t\t\t\tupdate_file(0, dst_other.sha1, dst_other.mode, new_path);\n \t\t\t} else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {\n \t\t\t\tren2 = item->util;\n \t\t\t\tclean_merge = 0;\n \t\t\t\tren2->processed = 1;\n-\t\t\t\toutput(1, \"CONFLICT (rename/rename): Renamed %s->%s in %s. \"\n+\t\t\t\toutput(1, o, \"CONFLICT (rename/rename): Renamed %s->%s in %s. \"\n \t\t\t\t       \"Renamed %s->%s in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren2->pair->one->path, ren2->pair->two->path, branch2);\n-\t\t\t\tconflict_rename_rename_2(ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename_2(ren1, branch1, ren2, branch2, o);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n \t\t\tif (try_merge) {\n-\t\t\t\tstruct diff_filespec *o, *a, *b;\n+\t\t\t\tstruct diff_filespec *one, *a, *b;\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tsrc_other.path = (char *)ren1_src;\n \n-\t\t\t\to = ren1->pair->one;\n+\t\t\t\tone = ren1->pair->one;\n \t\t\t\tif (a_renames == renames1) {\n \t\t\t\t\ta = ren1->pair->two;\n \t\t\t\t\tb = &src_other;\n@@ -973,8 +971,8 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\tb = ren1->pair->two;\n \t\t\t\t\ta = &src_other;\n \t\t\t\t}\n-\t\t\t\tmfi = merge_file(o, a, b,\n-\t\t\t\t\t\ta_branch, b_branch);\n+\t\t\t\tmfi = merge_file(one, a, b,\n+\t\t\t\t\t\to->branch1, o->branch2);\n \n \t\t\t\tif (mfi.clean &&\n \t\t\t\t    sha_eq(mfi.sha, ren1->pair->two->sha1) &&\n@@ -984,20 +982,20 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\t * t6022 test. If you change\n \t\t\t\t\t * it update the test too.\n \t\t\t\t\t */\n-\t\t\t\t\toutput(3, \"Skipped %s (merged same as existing)\", ren1_dst);\n+\t\t\t\t\toutput(3, o, \"Skipped %s (merged same as existing)\", ren1_dst);\n \t\t\t\telse {\n \t\t\t\t\tif (mfi.merge || !mfi.clean)\n-\t\t\t\t\t\toutput(1, \"Renamed %s => %s\", ren1_src, ren1_dst);\n+\t\t\t\t\t\toutput(1, o, \"Renamed %s => %s\", ren1_src, ren1_dst);\n \t\t\t\t\tif (mfi.merge)\n-\t\t\t\t\t\toutput(2, \"Auto-merged %s\", ren1_dst);\n+\t\t\t\t\t\toutput(2, o, \"Auto-merged %s\", ren1_dst);\n \t\t\t\t\tif (!mfi.clean) {\n-\t\t\t\t\t\toutput(1, \"CONFLICT (rename/modify): Merge conflict in %s\",\n+\t\t\t\t\t\toutput(1, o, \"CONFLICT (rename/modify): Merge conflict in %s\",\n \t\t\t\t\t\t       ren1_dst);\n \t\t\t\t\t\tclean_merge = 0;\n \n \t\t\t\t\t\tif (!index_only)\n \t\t\t\t\t\t\tupdate_stages(ren1_dst,\n-\t\t\t\t\t\t\t\t      o, a, b, 1);\n+\t\t\t\t\t\t\t\t      one, a, b, 1);\n \t\t\t\t\t}\n \t\t\t\t\tupdate_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);\n \t\t\t\t}\n@@ -1017,8 +1015,7 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)\n \n /* Per entry merge function */\n static int process_entry(const char *path, struct stage_data *entry,\n-\t\t\t const char *branch1,\n-\t\t\t const char *branch2)\n+\t\t\t struct merge_options *o)\n {\n \t/*\n \tprintf(\"processing entry, clean cache: %s\\n\", index_only ? \"yes\": \"no\");\n@@ -1040,23 +1037,23 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\t\t/* Deleted in both or deleted in one and\n \t\t\t * unchanged in the other */\n \t\t\tif (a_sha)\n-\t\t\t\toutput(2, \"Removed %s\", path);\n+\t\t\t\toutput(2, o, \"Removed %s\", path);\n \t\t\t/* do not touch working file if it did not exist */\n \t\t\tremove_file(1, path, !a_sha);\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n \t\t\tif (!a_sha) {\n-\t\t\t\toutput(1, \"CONFLICT (delete/modify): %s deleted in %s \"\n+\t\t\t\toutput(1, o, \"CONFLICT (delete/modify): %s deleted in %s \"\n \t\t\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n-\t\t\t\t       path, branch1,\n-\t\t\t\t       branch2, branch2, path);\n+\t\t\t\t       path, o->branch1,\n+\t\t\t\t       o->branch2, o->branch2, path);\n \t\t\t\tupdate_file(0, b_sha, b_mode, path);\n \t\t\t} else {\n-\t\t\t\toutput(1, \"CONFLICT (delete/modify): %s deleted in %s \"\n+\t\t\t\toutput(1, o, \"CONFLICT (delete/modify): %s deleted in %s \"\n \t\t\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n-\t\t\t\t       path, branch2,\n-\t\t\t\t       branch1, branch1, path);\n+\t\t\t\t       path, o->branch2,\n+\t\t\t\t       o->branch1, o->branch1, path);\n \t\t\t\tupdate_file(0, a_sha, a_mode, path);\n \t\t\t}\n \t\t}\n@@ -1071,14 +1068,14 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\tconst char *conf;\n \n \t\tif (a_sha) {\n-\t\t\tadd_branch = branch1;\n-\t\t\tother_branch = branch2;\n+\t\t\tadd_branch = o->branch1;\n+\t\t\tother_branch = o->branch2;\n \t\t\tmode = a_mode;\n \t\t\tsha = a_sha;\n \t\t\tconf = \"file/directory\";\n \t\t} else {\n-\t\t\tadd_branch = branch2;\n-\t\t\tother_branch = branch1;\n+\t\t\tadd_branch = o->branch2;\n+\t\t\tother_branch = o->branch1;\n \t\t\tmode = b_mode;\n \t\t\tsha = b_sha;\n \t\t\tconf = \"directory/file\";\n@@ -1086,13 +1083,13 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\tif (string_list_has_string(&current_directory_set, path)) {\n \t\t\tconst char *new_path = unique_path(path, add_branch);\n \t\t\tclean_merge = 0;\n-\t\t\toutput(1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n+\t\t\toutput(1, o, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n \t\t\t       \"Added %s as %s\",\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tremove_file(0, path, 0);\n \t\t\tupdate_file(0, sha, mode, new_path);\n \t\t} else {\n-\t\t\toutput(2, \"Added %s\", path);\n+\t\t\toutput(2, o, \"Added %s\", path);\n \t\t\tupdate_file(1, sha, mode, path);\n \t\t}\n \t} else if (a_sha && b_sha) {\n@@ -1100,32 +1097,32 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\t/* case D: Modified in both, but differently. */\n \t\tconst char *reason = \"content\";\n \t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec o, a, b;\n+\t\tstruct diff_filespec one, a, b;\n \n \t\tif (!o_sha) {\n \t\t\treason = \"add/add\";\n \t\t\to_sha = (unsigned char *)null_sha1;\n \t\t}\n-\t\toutput(2, \"Auto-merged %s\", path);\n-\t\to.path = a.path = b.path = (char *)path;\n-\t\thashcpy(o.sha1, o_sha);\n-\t\to.mode = o_mode;\n+\t\toutput(2, o, \"Auto-merged %s\", path);\n+\t\tone.path = a.path = b.path = (char *)path;\n+\t\thashcpy(one.sha1, o_sha);\n+\t\tone.mode = o_mode;\n \t\thashcpy(a.sha1, a_sha);\n \t\ta.mode = a_mode;\n \t\thashcpy(b.sha1, b_sha);\n \t\tb.mode = b_mode;\n \n-\t\tmfi = merge_file(&o, &a, &b,\n-\t\t\t\t branch1, branch2);\n+\t\tmfi = merge_file(&one, &a, &b,\n+\t\t\t\t o->branch1, o->branch2);\n \n \t\tclean_merge = mfi.clean;\n \t\tif (mfi.clean)\n \t\t\tupdate_file(1, mfi.sha, mfi.mode, path);\n \t\telse if (S_ISGITLINK(mfi.mode))\n-\t\t\toutput(1, \"CONFLICT (submodule): Merge conflict in %s \"\n+\t\t\toutput(1, o, \"CONFLICT (submodule): Merge conflict in %s \"\n \t\t\t       \"- needs %s\", path, sha1_to_hex(b.sha1));\n \t\telse {\n-\t\t\toutput(1, \"CONFLICT (%s): Merge conflict in %s\",\n+\t\t\toutput(1, o, \"CONFLICT (%s): Merge conflict in %s\",\n \t\t\t\t\treason, path);\n \n \t\t\tif (index_only)\n@@ -1146,11 +1143,10 @@ static int process_entry(const char *path, struct stage_data *entry,\n \treturn clean_merge;\n }\n \n-int merge_trees(struct tree *head,\n+int merge_trees(struct merge_options *o,\n+\t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *common,\n-\t\tconst char *branch1,\n-\t\tconst char *branch2,\n \t\tstruct tree **result)\n {\n \tint code, clean;\n@@ -1161,7 +1157,7 @@ int merge_trees(struct tree *head,\n \t}\n \n \tif (sha_eq(common->object.sha1, merge->object.sha1)) {\n-\t\toutput(0, \"Already uptodate!\");\n+\t\toutput(0, o, \"Already uptodate!\");\n \t\t*result = head;\n \t\treturn 1;\n \t}\n@@ -1182,15 +1178,14 @@ int merge_trees(struct tree *head,\n \t\tget_files_dirs(merge);\n \n \t\tentries = get_unmerged();\n-\t\tre_head  = get_renames(head, common, head, merge, entries);\n-\t\tre_merge = get_renames(merge, common, head, merge, entries);\n-\t\tclean = process_renames(re_head, re_merge,\n-\t\t\t\tbranch1, branch2);\n+\t\tre_head  = get_renames(head, common, head, merge, entries, o);\n+\t\tre_merge = get_renames(merge, common, head, merge, entries, o);\n+\t\tclean = process_renames(re_head, re_merge, o);\n \t\tfor (i = 0; i < entries->nr; i++) {\n \t\t\tconst char *path = entries->items[i].string;\n \t\t\tstruct stage_data *e = entries->items[i].util;\n \t\t\tif (!e->processed\n-\t\t\t\t&& !process_entry(path, e, branch1, branch2))\n+\t\t\t\t&& !process_entry(path, e, o))\n \t\t\t\tclean = 0;\n \t\t}\n \n@@ -1203,7 +1198,7 @@ int merge_trees(struct tree *head,\n \t\tclean = 1;\n \n \tif (index_only)\n-\t\t*result = write_tree_from_memory();\n+\t\t*result = write_tree_from_memory(o);\n \n \treturn clean;\n }\n@@ -1223,10 +1218,9 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n  * Merge the commits h1 and h2, return the resulting virtual\n  * commit object and a flag indicating the cleanness of the merge.\n  */\n-int merge_recursive(struct commit *h1,\n+int merge_recursive(struct merge_options *o,\n+\t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    const char *branch1,\n-\t\t    const char *branch2,\n \t\t    struct commit_list *ca,\n \t\t    struct commit **result)\n {\n@@ -1235,8 +1229,8 @@ int merge_recursive(struct commit *h1,\n \tstruct tree *mrtree = mrtree;\n \tint clean;\n \n-\tif (show(4)) {\n-\t\toutput(4, \"Merging:\");\n+\tif (show(4, o)) {\n+\t\toutput(4, o, \"Merging:\");\n \t\toutput_commit_title(h1);\n \t\toutput_commit_title(h2);\n \t}\n@@ -1246,8 +1240,8 @@ int merge_recursive(struct commit *h1,\n \t\tca = reverse_commit_list(ca);\n \t}\n \n-\tif (show(5)) {\n-\t\toutput(5, \"found %u common ancestor(s):\", commit_list_count(ca));\n+\tif (show(5, o)) {\n+\t\toutput(5, o, \"found %u common ancestor(s):\", commit_list_count(ca));\n \t\tfor (iter = ca; iter; iter = iter->next)\n \t\t\toutput_commit_title(iter->item);\n \t}\n@@ -1264,6 +1258,7 @@ int merge_recursive(struct commit *h1,\n \t}\n \n \tfor (iter = ca; iter; iter = iter->next) {\n+\t\tstruct merge_options opts;\n \t\tcall_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n@@ -1273,10 +1268,11 @@ int merge_recursive(struct commit *h1,\n \t\t * \"conflicts\" were already resolved.\n \t\t */\n \t\tdiscard_cache();\n-\t\tmerge_recursive(merged_common_ancestors, iter->item,\n-\t\t\t\t\"Temporary merge branch 1\",\n-\t\t\t\t\"Temporary merge branch 2\",\n-\t\t\t\tNULL,\n+\t\tmemcpy(&opts, o, sizeof(struct merge_options));\n+\t\topts.branch1 = \"Temporary merge branch 1\";\n+\t\topts.branch2 = \"Temporary merge branch 2\";\n+\t\tmerge_recursive(&opts, merged_common_ancestors,\n+\t\t\t\titer->item, NULL,\n \t\t\t\t&merged_common_ancestors);\n \t\tcall_depth--;\n \n@@ -1291,8 +1287,8 @@ int merge_recursive(struct commit *h1,\n \t} else\n \t\tindex_only = 1;\n \n-\tclean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,\n-\t\t\t    branch1, branch2, &mrtree);\n+\tclean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,\n+\t\t\t    &mrtree);\n \n \tif (index_only) {\n \t\t*result = make_virtual_commit(mrtree, \"merged tree\");\n@@ -1319,35 +1315,33 @@ static struct commit *get_ref(const unsigned char *sha1, const char *name)\n \treturn (struct commit *)object;\n }\n \n-int merge_recursive_generic(const char **base_list,\n-\t\tconst unsigned char *head_sha1, const char *head_name,\n-\t\tconst unsigned char *next_sha1, const char *next_name)\n+int merge_recursive_generic(struct merge_options *o,\n+\t\t\t    const unsigned char *head,\n+\t\t\t    const unsigned char *merge,\n+\t\t\t    int num_base_list,\n+\t\t\t    const unsigned char **base_list,\n+\t\t\t    struct commit **result)\n {\n \tint clean, index_fd;\n \tstruct lock_file *lock = xcalloc(1, sizeof(struct lock_file));\n-\tstruct commit *result;\n-\tstruct commit *head_commit = get_ref(head_sha1, head_name);\n-\tstruct commit *next_commit = get_ref(next_sha1, next_name);\n+\tstruct commit *head_commit = get_ref(head, o->branch1);\n+\tstruct commit *next_commit = get_ref(merge, o->branch2);\n \tstruct commit_list *ca = NULL;\n \n \tif (base_list) {\n \t\tint i;\n-\t\tfor (i = 0; base_list[i]; ++i) {\n-\t\t\tunsigned char sha[20];\n+\t\tfor (i = 0; i < num_base_list; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (get_sha1(base_list[i], sha))\n-\t\t\t\treturn error(\"Could not resolve ref '%s'\",\n-\t\t\t\t\t\t\t\tbase_list[i]);\n-\t\t\tif (!(base = get_ref(sha, base_list[i])))\n+\t\t\tif (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))\n \t\t\t\treturn error(\"Could not parse object '%s'\",\n-\t\t\t\t\t\t\t\tbase_list[i]);\n+\t\t\t\t\tsha1_to_hex(base_list[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t}\n \n \tindex_fd = hold_locked_index(lock, 1);\n-\tclean = merge_recursive(head_commit, next_commit,\n-\t\t\t\thead_name, next_name, ca, &result);\n+\tclean = merge_recursive(o, head_commit, next_commit, ca,\n+\t\t\tresult);\n \tif (active_cache_changed &&\n \t\t\t(write_cache(index_fd, active_cache, active_nr) ||\n \t\t\t commit_locked_index(lock)))\n@@ -1356,29 +1350,35 @@ int merge_recursive_generic(const char **base_list,\n \treturn clean ? 0 : 1;\n }\n \n-int merge_recursive_config(const char *var, const char *value, void *cb)\n+static int merge_recursive_config(const char *var, const char *value, void *cb)\n {\n+\tstruct merge_options *o = cb;\n \tif (!strcasecmp(var, \"merge.verbosity\")) {\n-\t\tmerge_recursive_verbosity = git_config_int(var, value);\n+\t\to->verbosity = git_config_int(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcasecmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit = git_config_int(var, value);\n+\t\to->diff_rename_limit = git_config_int(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcasecmp(var, \"merge.renamelimit\")) {\n-\t\tmerge_rename_limit = git_config_int(var, value);\n+\t\to->merge_rename_limit = git_config_int(var, value);\n \t\treturn 0;\n \t}\n \treturn git_default_config(var, value, cb);\n }\n \n-void merge_recursive_setup(int is_subtree_merge)\n+void init_merge_options(struct merge_options *o)\n {\n+\tmemset(o, 0, sizeof(struct merge_options));\n+\to->verbosity = 2;\n+\to->buffer_output = 1;\n+\to->diff_rename_limit = -1;\n+\to->merge_rename_limit = -1;\n+\tgit_config(merge_recursive_config, o);\n \tif (getenv(\"GIT_MERGE_VERBOSITY\"))\n-\t\tmerge_recursive_verbosity =\n+\t\to->verbosity =\n \t\t\tstrtol(getenv(\"GIT_MERGE_VERBOSITY\"), NULL, 10);\n-\tif (merge_recursive_verbosity >= 5)\n-\t\tbuffer_output = 0;\n-\tsubtree_merge = is_subtree_merge;\n+\tif (o->verbosity >= 5)\n+\t\to->buffer_output = 0;\n }\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 4dd6476..72f0a28 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -1,26 +1,42 @@\n #ifndef MERGE_RECURSIVE_H\n #define MERGE_RECURSIVE_H\n \n-int merge_recursive(struct commit *h1,\n+struct merge_options {\n+\tconst char *branch1;\n+\tconst char *branch2;\n+\tunsigned subtree_merge : 1;\n+\tunsigned buffer_output : 1;\n+\tint verbosity;\n+\tint diff_rename_limit;\n+\tint merge_rename_limit;\n+};\n+\n+/* merge_trees() but with recursive ancestor consolidation */\n+int merge_recursive(struct merge_options *o,\n+\t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    const char *branch1,\n-\t\t    const char *branch2,\n \t\t    struct commit_list *ancestors,\n \t\t    struct commit **result);\n \n-int merge_trees(struct tree *head,\n+/* rename-detecting three-way merge, no recursion */\n+int merge_trees(struct merge_options *o,\n+\t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *common,\n-\t\tconst char *branch1,\n-\t\tconst char *branch2,\n \t\tstruct tree **result);\n-extern int merge_recursive_generic(const char **base_list,\n-\t\tconst unsigned char *head_sha1, const char *head_name,\n-\t\tconst unsigned char *next_sha1, const char *next_name);\n-int merge_recursive_config(const char *var, const char *value, void *cb);\n-void merge_recursive_setup(int is_subtree_merge);\n-struct tree *write_tree_from_memory(void);\n \n-extern int merge_recursive_verbosity;\n+/*\n+ * \"git-merge-recursive\" can be fed trees; wrap them into\n+ * virtual commits and call merge_recursive() proper.\n+ */\n+int merge_recursive_generic(struct merge_options *o,\n+\t\t\t    const unsigned char *head,\n+\t\t\t    const unsigned char *merge,\n+\t\t\t    int num_ca,\n+\t\t\t    const unsigned char **ca,\n+\t\t\t    struct commit **result);\n+\n+void init_merge_options(struct merge_options *o);\n+struct tree *write_tree_from_memory(struct merge_options *o);\n \n #endif\n-- \n1.6.0.rc3.17.gc14c8.dirty\n"},{"id":"88432","messageId":"7v7ia5iq7l.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"1219628677-23903-1-git-send-email-vmiklos@frugalware.org","subject":"Re: [PATCH] merge-recursive: introduce merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-25T06:06:06Z","receivedAt":"2008-08-25T06:06:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> 1) This applies on top of 1c868d4 (merge-recursive.c: Add more generic\n> merge_recursive_generic()). I can rebase this (along with 1c868d4 and\n> 1c868d4^) on top of current master, if this is a problem.\n\nIt probably is cleaner to treat this as a fresh topic from scratch on top\nof 'master', as we do not have anything outstanding in 'next' around this\narea.\n\n> 2) I know that this patch is huge, but we want to have the verbosity\n> flag in merge_options, so it has to be passed as an argument in many\n> places.\n\nSize of the patch that results purely from addition of the merge_options\nparameter from top to bottom does not bother me too much.  The look quite\nstraightforward conversions, and getting rid of these many global\nvariables is a major step in the right direction.\n\nIt might however be a good idea to consistently have this at the same\nplace (either the beginning or at the end) of the parameter list of\nfunctions that take one.\n\n> @@ -1273,10 +1268,11 @@ int merge_recursive(struct commit *h1,\n>  \t\t * \"conflicts\" were already resolved.\n>  \t\t */\n>  \t\tdiscard_cache();\n> -\t\tmerge_recursive(merged_common_ancestors, iter->item,\n> -\t\t\t\t\"Temporary merge branch 1\",\n> -\t\t\t\t\"Temporary merge branch 2\",\n> -\t\t\t\tNULL,\n> +\t\tmemcpy(&opts, o, sizeof(struct merge_options));\n> +\t\topts.branch1 = \"Temporary merge branch 1\";\n> +\t\topts.branch2 = \"Temporary merge branch 2\";\n> +\t\tmerge_recursive(&opts, merged_common_ancestors,\n> +\t\t\t\titer->item, NULL,\n>  \t\t\t\t&merged_common_ancestors);\n>  \t\tcall_depth--;\n\nAfter suggesting to keep label in merge_options, I was wondering how this\npart should be handled the best.  An alternative would be not to do copy\nthe structure but stash away only branch1 and branch2 members before\nmaking the recursive call and restore them after it returns, like this:\n\n\t\tconst char *saved_b1, *saved_b2;\n\t\t...\n\t\tsaved_b1 = o->branch1;\n\t\tsaved_b2 = o->branch2;\n\t\to->branch1 = \"Temporary merge branch 1\";\n\t\to->branch2 = \"Temporary merge branch 2\";\n\t\tmerge_recursive(o, ...);\n\t\to->branch1 = saved_b1;\n\t\to->branch2 = saved_b2;\n\t\tcall_depth--;\n\t\t...\n\t\t\nThis might be better in the longer run, as we may want to pass *back*\nstatus from merge_recursive() to the caller in fields of merge_options in\nthe future.\n"},{"id":"88460","messageId":"1219674357-31000-1-git-send-email-vmiklos@frugalware.org","threadId":"15081","inReplyTo":"7v7ia5iq7l.fsf@gitster.siamese.dyndns.org","subject":"[PATCH] merge-recursive: introduce merge_options","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-25T14:25:57Z","receivedAt":"2008-08-25T14:25:57Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"This makes it possible to avoid passing the labels of branches as\narguments to merge_recursive(), merge_trees() and\nmerge_recursive_generic().\n\nIt also takes care of subtree merge, output buffering, verbosity, and\nrename limits - these were global variables till now in\nmerge-recursive.c.\n\nA new function, named init_merge_options(), is introduced as well, it\nclears the struct merge_info, then initializes with default values,\nfinally updates the default values based on the config and environment\nvariables.\n\nSigned-off-by: Miklos Vajna <vmiklos@frugalware.org>\n---\n\nOn Sun, Aug 24, 2008 at 11:06:06PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> Miklos Vajna <vmiklos@frugalware.org> writes:\n>\n> > 1) This applies on top of 1c868d4 (merge-recursive.c: Add more\n> > generic\n> > merge_recursive_generic()). I can rebase this (along with 1c868d4\n> > and\n> > 1c868d4^) on top of current master, if this is a problem.\n>\n> It probably is cleaner to treat this as a fresh topic from scratch on\n> top\n> of 'master', as we do not have anything outstanding in 'next' around\n> this\n> area.\n\nI'm now confused about what should I do:\n\n1) Nothing. (That's what I did for now.)\n\n2) Rebase against master and resend.\n\n3) Rebase, squash and resend.\n\n> > 2) I know that this patch is huge, but we want to have the verbosity\n> > flag in merge_options, so it has to be passed as an argument in many\n> > places.\n>\n> Size of the patch that results purely from addition of the\n> merge_options\n> parameter from top to bottom does not bother me too much.  The look\n> quite\n> straightforward conversions, and getting rid of these many global\n> variables is a major step in the right direction.\n>\n> It might however be a good idea to consistently have this at the same\n> place (either the beginning or at the end) of the parameter list of\n> functions that take one.\n\nOK, I'll move them to the beginning, since that's what we have for the\npublic functions already.\n\n> > @@ -1273,10 +1268,11 @@ int merge_recursive(struct commit *h1,\n> >              * \"conflicts\" were already resolved.\n> >              */\n> >             discard_cache();\n> > -           merge_recursive(merged_common_ancestors, iter->item,\n> > -                           \"Temporary merge branch 1\",\n> > -                           \"Temporary merge branch 2\",\n> > -                           NULL,\n> > +           memcpy(&opts, o, sizeof(struct merge_options));\n> > +           opts.branch1 = \"Temporary merge branch 1\";\n> > +           opts.branch2 = \"Temporary merge branch 2\";\n> > +           merge_recursive(&opts, merged_common_ancestors,\n> > +                           iter->item, NULL,\n> >                             &merged_common_ancestors);\n> >             call_depth--;\n>\n> After suggesting to keep label in merge_options, I was wondering how\n> this\n> part should be handled the best.  An alternative would be not to do\n> copy\n> the structure but stash away only branch1 and branch2 members before\n> making the recursive call and restore them after it returns, like\n> this:\n>\n>               const char *saved_b1, *saved_b2;\n>               ...\n>               saved_b1 = o->branch1;\n>               saved_b2 = o->branch2;\n>               o->branch1 = \"Temporary merge branch 1\";\n>               o->branch2 = \"Temporary merge branch 2\";\n>               merge_recursive(o, ...);\n>               o->branch1 = saved_b1;\n>               o->branch2 = saved_b2;\n>               call_depth--;\n>               ...\n>\n> This might be better in the longer run, as we may want to pass *back*\n> status from merge_recursive() to the caller in fields of merge_options\n> in\n> the future.\n\nTrue, changed.\n\nInterdiff:\n\ngit fetch git://repo.or.cz/git/vmiklos.git\ngit diff 2ff6553..8faf3ac\n\n builtin-checkout.c        |   11 ++-\n builtin-merge-recursive.c |   43 ++++----\n merge-recursive.c         |  260 +++++++++++++++++++++++----------------------\n merge-recursive.h         |   42 +++++---\n 4 files changed, 190 insertions(+), 166 deletions(-)\n\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 411cc51..3627996 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -264,6 +264,7 @@ static int merge_working_tree(struct checkout_opts *opts,\n \t\t\t */\n \t\t\tstruct tree *result;\n \t\t\tstruct tree *work;\n+\t\t\tstruct merge_options o;\n \t\t\tif (!opts->merge)\n \t\t\t\treturn 1;\n \t\t\tparse_commit(old->commit);\n@@ -282,13 +283,17 @@ static int merge_working_tree(struct checkout_opts *opts,\n \t\t\t */\n \n \t\t\tadd_files_to_cache(NULL, NULL, 0);\n-\t\t\twork = write_tree_from_memory();\n+\t\t\tinit_merge_options(&o);\n+\t\t\to.verbosity = 0;\n+\t\t\twork = write_tree_from_memory(&o);\n \n \t\t\tret = reset_tree(new->commit->tree, opts, 1);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\n-\t\t\tmerge_trees(new->commit->tree, work, old->commit->tree,\n-\t\t\t\t    new->name, \"local\", &result);\n+\t\t\to.branch1 = new->name;\n+\t\t\to.branch2 = \"local\";\n+\t\t\tmerge_trees(&o, new->commit->tree, work,\n+\t\t\t\told->commit->tree, &result);\n \t\t\tret = reset_tree(new->commit->tree, opts, 0);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\ndiff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c\nindex 25f540b..6b534c1 100644\n--- a/builtin-merge-recursive.c\n+++ b/builtin-merge-recursive.c\n@@ -17,32 +17,33 @@ static const char *better_branch_name(const char *branch)\n \n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *bases[21];\n+\tconst unsigned char *bases[21];\n \tunsigned bases_count = 0;\n \tint i, failed;\n-\tconst char *branch1, *branch2;\n \tunsigned char h1[20], h2[20];\n-\tint subtree_merge = 0;\n+\tstruct merge_options o;\n+\tstruct commit *result;\n \n+\tinit_merge_options(&o);\n \tif (argv[0]) {\n \t\tint namelen = strlen(argv[0]);\n \t\tif (8 < namelen &&\n \t\t    !strcmp(argv[0] + namelen - 8, \"-subtree\"))\n-\t\t\tsubtree_merge = 1;\n+\t\t\to.subtree_merge = 1;\n \t}\n \n-\tgit_config(merge_recursive_config, NULL);\n-\tmerge_recursive_setup(subtree_merge);\n \tif (argc < 4)\n \t\tdie(\"Usage: %s <base>... -- <head> <remote> ...\\n\", argv[0]);\n \n \tfor (i = 1; i < argc; ++i) {\n-\t\tif (!strcmp(argv[i], \"--\")) {\n-\t\t\tbases[bases_count] = NULL;\n+\t\tif (!strcmp(argv[i], \"--\"))\n \t\t\tbreak;\n+\t\tif (bases_count < ARRAY_SIZE(bases)-1) {\n+\t\t\tunsigned char *sha = xmalloc(20);\n+\t\t\tif (get_sha1(argv[i], sha))\n+\t\t\t\tdie(\"Could not parse object '%s'\", argv[i]);\n+\t\t\tbases[bases_count++] = sha;\n \t\t}\n-\t\tif (bases_count < ARRAY_SIZE(bases)-1)\n-\t\t\tbases[bases_count++] = argv[i];\n \t\telse\n \t\t\twarning(\"Cannot handle more than %zu bases. \"\n \t\t\t\t\"Ignoring %s.\", ARRAY_SIZE(bases)-1, argv[i]);\n@@ -50,21 +51,21 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n \t\tdie(\"Not handling anything other than two heads merge.\");\n \n-\tbranch1 = argv[++i];\n-\tbranch2 = argv[++i];\n+\to.branch1 = argv[++i];\n+\to.branch2 = argv[++i];\n \n-\tif (get_sha1(branch1, h1))\n-\t\tdie(\"Could not resolve ref '%s'\", branch1);\n-\tif (get_sha1(branch2, h2))\n-\t\tdie(\"Could not resolve ref '%s'\", branch2);\n+\tif (get_sha1(o.branch1, h1))\n+\t\tdie(\"Could not resolve ref '%s'\", o.branch1);\n+\tif (get_sha1(o.branch2, h2))\n+\t\tdie(\"Could not resolve ref '%s'\", o.branch2);\n \n-\tbranch1 = better_branch_name(branch1);\n-\tbranch2 = better_branch_name(branch2);\n+\to.branch1 = better_branch_name(o.branch1);\n+\to.branch2 = better_branch_name(o.branch2);\n \n-\tif (merge_recursive_verbosity >= 3)\n-\t\tprintf(\"Merging %s with %s\\n\", branch1, branch2);\n+\tif (o.verbosity >= 3)\n+\t\tprintf(\"Merging %s with %s\\n\", o.branch1, o.branch2);\n \n-\tfailed = merge_recursive_generic(bases, h1, branch1, h2, branch2);\n+\tfailed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);\n \tif (failed < 0)\n \t\treturn 128; /* die() error code */\n \treturn failed;\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 74a9fdc..5fab301 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -83,16 +83,11 @@ static struct string_list current_file_set = {NULL, 0, 0, 1};\n static struct string_list current_directory_set = {NULL, 0, 0, 1};\n \n static int call_depth = 0;\n-int merge_recursive_verbosity = 2;\n-static int diff_rename_limit = -1;\n-static int merge_rename_limit = -1;\n-static int buffer_output = 1;\n static struct strbuf obuf = STRBUF_INIT;\n \n-static int show(int v)\n+static int show(struct merge_options *o, int v)\n {\n-\treturn (!call_depth && merge_recursive_verbosity >= v) ||\n-\t\tmerge_recursive_verbosity >= 5;\n+\treturn (!call_depth && o->verbosity >= v) || o->verbosity >= 5;\n }\n \n static void flush_output(void)\n@@ -103,12 +98,12 @@ static void flush_output(void)\n \t}\n }\n \n-static void output(int v, const char *fmt, ...)\n+static void output(struct merge_options *o, int v, const char *fmt, ...)\n {\n \tint len;\n \tva_list ap;\n \n-\tif (!show(v))\n+\tif (!show(o, v))\n \t\treturn;\n \n \tstrbuf_grow(&obuf, call_depth * 2 + 2);\n@@ -132,7 +127,7 @@ static void output(int v, const char *fmt, ...)\n \t}\n \tstrbuf_setlen(&obuf, obuf.len + len);\n \tstrbuf_add(&obuf, \"\\n\", 1);\n-\tif (!buffer_output)\n+\tif (!o->buffer_output)\n \t\tflush_output();\n }\n \n@@ -219,17 +214,17 @@ static int git_merge_trees(int index_only,\n \treturn rc;\n }\n \n-struct tree *write_tree_from_memory(void)\n+struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n \n \tif (unmerged_cache()) {\n \t\tint i;\n-\t\toutput(0, \"There are unmerged index entries:\");\n+\t\toutput(o, 0, \"There are unmerged index entries:\");\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tif (ce_stage(ce))\n-\t\t\t\toutput(0, \"%d %.*s\", ce_stage(ce), ce_namelen(ce), ce->name);\n+\t\t\t\toutput(o, 0, \"%d %.*s\", ce_stage(ce), ce_namelen(ce), ce->name);\n \t\t}\n \t\treturn NULL;\n \t}\n@@ -341,11 +336,12 @@ struct rename\n  * 'b_tree') to be able to associate the correct cache entries with\n  * the rename information. 'tree' is always equal to either a_tree or b_tree.\n  */\n-static struct string_list *get_renames(struct tree *tree,\n-\t\t\t\t\tstruct tree *o_tree,\n-\t\t\t\t\tstruct tree *a_tree,\n-\t\t\t\t\tstruct tree *b_tree,\n-\t\t\t\t\tstruct string_list *entries)\n+static struct string_list *get_renames(struct merge_options *o,\n+\t\t\t\t       struct tree *tree,\n+\t\t\t\t       struct tree *o_tree,\n+\t\t\t\t       struct tree *a_tree,\n+\t\t\t\t       struct tree *b_tree,\n+\t\t\t\t       struct string_list *entries)\n {\n \tint i;\n \tstruct string_list *renames;\n@@ -355,8 +351,8 @@ static struct string_list *get_renames(struct tree *tree,\n \tdiff_setup(&opts);\n \tDIFF_OPT_SET(&opts, RECURSIVE);\n \topts.detect_rename = DIFF_DETECT_RENAME;\n-\topts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit :\n-\t\t\t    diff_rename_limit >= 0 ? diff_rename_limit :\n+\topts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :\n+\t\t\t    o->diff_rename_limit >= 0 ? o->diff_rename_limit :\n \t\t\t    500;\n \topts.warn_on_too_large_rename = 1;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -717,7 +713,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o,\n \treturn result;\n }\n \n-static void conflict_rename_rename(struct rename *ren1,\n+static void conflict_rename_rename(struct merge_options *o,\n+\t\t\t\t   struct rename *ren1,\n \t\t\t\t   const char *branch1,\n \t\t\t\t   struct rename *ren2,\n \t\t\t\t   const char *branch2)\n@@ -730,13 +727,13 @@ static void conflict_rename_rename(struct rename *ren1,\n \tconst char *dst_name2 = ren2_dst;\n \tif (string_list_has_string(&current_directory_set, ren1_dst)) {\n \t\tdst_name1 = del[delp++] = unique_path(ren1_dst, branch1);\n-\t\toutput(1, \"%s is a directory in %s added as %s instead\",\n+\t\toutput(o, 1, \"%s is a directory in %s added as %s instead\",\n \t\t       ren1_dst, branch2, dst_name1);\n \t\tremove_file(0, ren1_dst, 0);\n \t}\n \tif (string_list_has_string(&current_directory_set, ren2_dst)) {\n \t\tdst_name2 = del[delp++] = unique_path(ren2_dst, branch2);\n-\t\toutput(1, \"%s is a directory in %s added as %s instead\",\n+\t\toutput(o, 1, \"%s is a directory in %s added as %s instead\",\n \t\t       ren2_dst, branch1, dst_name2);\n \t\tremove_file(0, ren2_dst, 0);\n \t}\n@@ -757,24 +754,26 @@ static void conflict_rename_rename(struct rename *ren1,\n \t\tfree(del[delp]);\n }\n \n-static void conflict_rename_dir(struct rename *ren1,\n+static void conflict_rename_dir(struct merge_options *o,\n+\t\t\t\tstruct rename *ren1,\n \t\t\t\tconst char *branch1)\n {\n \tchar *new_path = unique_path(ren1->pair->two->path, branch1);\n-\toutput(1, \"Renamed %s to %s instead\", ren1->pair->one->path, new_path);\n+\toutput(o, 1, \"Renamed %s to %s instead\", ren1->pair->one->path, new_path);\n \tremove_file(0, ren1->pair->two->path, 0);\n \tupdate_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);\n \tfree(new_path);\n }\n \n-static void conflict_rename_rename_2(struct rename *ren1,\n+static void conflict_rename_rename_2(struct merge_options *o,\n+\t\t\t\t     struct rename *ren1,\n \t\t\t\t     const char *branch1,\n \t\t\t\t     struct rename *ren2,\n \t\t\t\t     const char *branch2)\n {\n \tchar *new_path1 = unique_path(ren1->pair->two->path, branch1);\n \tchar *new_path2 = unique_path(ren2->pair->two->path, branch2);\n-\toutput(1, \"Renamed %s to %s and %s to %s instead\",\n+\toutput(o, 1, \"Renamed %s to %s and %s to %s instead\",\n \t       ren1->pair->one->path, new_path1,\n \t       ren2->pair->one->path, new_path2);\n \tremove_file(0, ren1->pair->two->path, 0);\n@@ -784,10 +783,9 @@ static void conflict_rename_rename_2(struct rename *ren1,\n \tfree(new_path1);\n }\n \n-static int process_renames(struct string_list *a_renames,\n-\t\t\t   struct string_list *b_renames,\n-\t\t\t   const char *a_branch,\n-\t\t\t   const char *b_branch)\n+static int process_renames(struct merge_options *o,\n+\t\t\t   struct string_list *a_renames,\n+\t\t\t   struct string_list *b_renames)\n {\n \tint clean_merge = 1, i, j;\n \tstruct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};\n@@ -832,15 +830,15 @@ static int process_renames(struct string_list *a_renames,\n \t\t\trenames1 = a_renames;\n \t\t\trenames2 = b_renames;\n \t\t\trenames2Dst = &b_by_dst;\n-\t\t\tbranch1 = a_branch;\n-\t\t\tbranch2 = b_branch;\n+\t\t\tbranch1 = o->branch1;\n+\t\t\tbranch2 = o->branch2;\n \t\t} else {\n \t\t\tstruct rename *tmp;\n \t\t\trenames1 = b_renames;\n \t\t\trenames2 = a_renames;\n \t\t\trenames2Dst = &a_by_dst;\n-\t\t\tbranch1 = b_branch;\n-\t\t\tbranch2 = a_branch;\n+\t\t\tbranch1 = o->branch2;\n+\t\t\tbranch2 = o->branch1;\n \t\t\ttmp = ren2;\n \t\t\tren2 = ren1;\n \t\t\tren1 = tmp;\n@@ -867,7 +865,7 @@ static int process_renames(struct string_list *a_renames,\n \t\t\tren2->processed = 1;\n \t\t\tif (strcmp(ren1_dst, ren2_dst) != 0) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(1, \"CONFLICT (rename/rename): \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (rename/rename): \"\n \t\t\t\t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n \t\t\t\t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\",\n \t\t\t\t       src, ren1_dst, branch1,\n@@ -878,7 +876,7 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\tupdate_file(0, ren1->pair->one->sha1,\n \t\t\t\t\t\t    ren1->pair->one->mode, src);\n \t\t\t\t}\n-\t\t\t\tconflict_rename_rename(ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename(o, ren1, branch1, ren2, branch2);\n \t\t\t} else {\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tremove_file(1, ren1_src, 1);\n@@ -888,13 +886,13 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\t\t branch1,\n \t\t\t\t\t\t branch2);\n \t\t\t\tif (mfi.merge || !mfi.clean)\n-\t\t\t\t\toutput(1, \"Renamed %s->%s\", src, ren1_dst);\n+\t\t\t\t\toutput(o, 1, \"Renamed %s->%s\", src, ren1_dst);\n \n \t\t\t\tif (mfi.merge)\n-\t\t\t\t\toutput(2, \"Auto-merged %s\", ren1_dst);\n+\t\t\t\t\toutput(o, 2, \"Auto-merged %s\", ren1_dst);\n \n \t\t\t\tif (!mfi.clean) {\n-\t\t\t\t\toutput(1, \"CONFLICT (content): merge conflict in %s\",\n+\t\t\t\t\toutput(o, 1, \"CONFLICT (content): merge conflict in %s\",\n \t\t\t\t\t       ren1_dst);\n \t\t\t\t\tclean_merge = 0;\n \n@@ -925,14 +923,14 @@ static int process_renames(struct string_list *a_renames,\n \n \t\t\tif (string_list_has_string(&current_directory_set, ren1_dst)) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(1, \"CONFLICT (rename/directory): Renamed %s->%s in %s \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (rename/directory): Renamed %s->%s in %s \"\n \t\t\t\t       \" directory %s added in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren1_dst, branch2);\n-\t\t\t\tconflict_rename_dir(ren1, branch1);\n+\t\t\t\tconflict_rename_dir(o, ren1, branch1);\n \t\t\t} else if (sha_eq(src_other.sha1, null_sha1)) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(1, \"CONFLICT (rename/delete): Renamed %s->%s in %s \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (rename/delete): Renamed %s->%s in %s \"\n \t\t\t\t       \"and deleted in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       branch2);\n@@ -941,31 +939,31 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\tconst char *new_path;\n \t\t\t\tclean_merge = 0;\n \t\t\t\ttry_merge = 1;\n-\t\t\t\toutput(1, \"CONFLICT (rename/add): Renamed %s->%s in %s. \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (rename/add): Renamed %s->%s in %s. \"\n \t\t\t\t       \"%s added in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren1_dst, branch2);\n \t\t\t\tnew_path = unique_path(ren1_dst, branch2);\n-\t\t\t\toutput(1, \"Added as %s instead\", new_path);\n+\t\t\t\toutput(o, 1, \"Added as %s instead\", new_path);\n \t\t\t\tupdate_file(0, dst_other.sha1, dst_other.mode, new_path);\n \t\t\t} else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {\n \t\t\t\tren2 = item->util;\n \t\t\t\tclean_merge = 0;\n \t\t\t\tren2->processed = 1;\n-\t\t\t\toutput(1, \"CONFLICT (rename/rename): Renamed %s->%s in %s. \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (rename/rename): Renamed %s->%s in %s. \"\n \t\t\t\t       \"Renamed %s->%s in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren2->pair->one->path, ren2->pair->two->path, branch2);\n-\t\t\t\tconflict_rename_rename_2(ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename_2(o, ren1, branch1, ren2, branch2);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n \t\t\tif (try_merge) {\n-\t\t\t\tstruct diff_filespec *o, *a, *b;\n+\t\t\t\tstruct diff_filespec *one, *a, *b;\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tsrc_other.path = (char *)ren1_src;\n \n-\t\t\t\to = ren1->pair->one;\n+\t\t\t\tone = ren1->pair->one;\n \t\t\t\tif (a_renames == renames1) {\n \t\t\t\t\ta = ren1->pair->two;\n \t\t\t\t\tb = &src_other;\n@@ -973,8 +971,8 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\tb = ren1->pair->two;\n \t\t\t\t\ta = &src_other;\n \t\t\t\t}\n-\t\t\t\tmfi = merge_file(o, a, b,\n-\t\t\t\t\t\ta_branch, b_branch);\n+\t\t\t\tmfi = merge_file(one, a, b,\n+\t\t\t\t\t\to->branch1, o->branch2);\n \n \t\t\t\tif (mfi.clean &&\n \t\t\t\t    sha_eq(mfi.sha, ren1->pair->two->sha1) &&\n@@ -984,20 +982,20 @@ static int process_renames(struct string_list *a_renames,\n \t\t\t\t\t * t6022 test. If you change\n \t\t\t\t\t * it update the test too.\n \t\t\t\t\t */\n-\t\t\t\t\toutput(3, \"Skipped %s (merged same as existing)\", ren1_dst);\n+\t\t\t\t\toutput(o, 3, \"Skipped %s (merged same as existing)\", ren1_dst);\n \t\t\t\telse {\n \t\t\t\t\tif (mfi.merge || !mfi.clean)\n-\t\t\t\t\t\toutput(1, \"Renamed %s => %s\", ren1_src, ren1_dst);\n+\t\t\t\t\t\toutput(o, 1, \"Renamed %s => %s\", ren1_src, ren1_dst);\n \t\t\t\t\tif (mfi.merge)\n-\t\t\t\t\t\toutput(2, \"Auto-merged %s\", ren1_dst);\n+\t\t\t\t\t\toutput(o, 2, \"Auto-merged %s\", ren1_dst);\n \t\t\t\t\tif (!mfi.clean) {\n-\t\t\t\t\t\toutput(1, \"CONFLICT (rename/modify): Merge conflict in %s\",\n+\t\t\t\t\t\toutput(o, 1, \"CONFLICT (rename/modify): Merge conflict in %s\",\n \t\t\t\t\t\t       ren1_dst);\n \t\t\t\t\t\tclean_merge = 0;\n \n \t\t\t\t\t\tif (!index_only)\n \t\t\t\t\t\t\tupdate_stages(ren1_dst,\n-\t\t\t\t\t\t\t\t      o, a, b, 1);\n+\t\t\t\t\t\t\t\t      one, a, b, 1);\n \t\t\t\t\t}\n \t\t\t\t\tupdate_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);\n \t\t\t\t}\n@@ -1016,9 +1014,8 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)\n }\n \n /* Per entry merge function */\n-static int process_entry(const char *path, struct stage_data *entry,\n-\t\t\t const char *branch1,\n-\t\t\t const char *branch2)\n+static int process_entry(struct merge_options *o,\n+\t\t\t const char *path, struct stage_data *entry)\n {\n \t/*\n \tprintf(\"processing entry, clean cache: %s\\n\", index_only ? \"yes\": \"no\");\n@@ -1040,23 +1037,23 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\t\t/* Deleted in both or deleted in one and\n \t\t\t * unchanged in the other */\n \t\t\tif (a_sha)\n-\t\t\t\toutput(2, \"Removed %s\", path);\n+\t\t\t\toutput(o, 2, \"Removed %s\", path);\n \t\t\t/* do not touch working file if it did not exist */\n \t\t\tremove_file(1, path, !a_sha);\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n \t\t\tif (!a_sha) {\n-\t\t\t\toutput(1, \"CONFLICT (delete/modify): %s deleted in %s \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n \t\t\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n-\t\t\t\t       path, branch1,\n-\t\t\t\t       branch2, branch2, path);\n+\t\t\t\t       path, o->branch1,\n+\t\t\t\t       o->branch2, o->branch2, path);\n \t\t\t\tupdate_file(0, b_sha, b_mode, path);\n \t\t\t} else {\n-\t\t\t\toutput(1, \"CONFLICT (delete/modify): %s deleted in %s \"\n+\t\t\t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n \t\t\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n-\t\t\t\t       path, branch2,\n-\t\t\t\t       branch1, branch1, path);\n+\t\t\t\t       path, o->branch2,\n+\t\t\t\t       o->branch1, o->branch1, path);\n \t\t\t\tupdate_file(0, a_sha, a_mode, path);\n \t\t\t}\n \t\t}\n@@ -1071,14 +1068,14 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\tconst char *conf;\n \n \t\tif (a_sha) {\n-\t\t\tadd_branch = branch1;\n-\t\t\tother_branch = branch2;\n+\t\t\tadd_branch = o->branch1;\n+\t\t\tother_branch = o->branch2;\n \t\t\tmode = a_mode;\n \t\t\tsha = a_sha;\n \t\t\tconf = \"file/directory\";\n \t\t} else {\n-\t\t\tadd_branch = branch2;\n-\t\t\tother_branch = branch1;\n+\t\t\tadd_branch = o->branch2;\n+\t\t\tother_branch = o->branch1;\n \t\t\tmode = b_mode;\n \t\t\tsha = b_sha;\n \t\t\tconf = \"directory/file\";\n@@ -1086,13 +1083,13 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\tif (string_list_has_string(&current_directory_set, path)) {\n \t\t\tconst char *new_path = unique_path(path, add_branch);\n \t\t\tclean_merge = 0;\n-\t\t\toutput(1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n+\t\t\toutput(o, 1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n \t\t\t       \"Added %s as %s\",\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tremove_file(0, path, 0);\n \t\t\tupdate_file(0, sha, mode, new_path);\n \t\t} else {\n-\t\t\toutput(2, \"Added %s\", path);\n+\t\t\toutput(o, 2, \"Added %s\", path);\n \t\t\tupdate_file(1, sha, mode, path);\n \t\t}\n \t} else if (a_sha && b_sha) {\n@@ -1100,32 +1097,32 @@ static int process_entry(const char *path, struct stage_data *entry,\n \t\t/* case D: Modified in both, but differently. */\n \t\tconst char *reason = \"content\";\n \t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec o, a, b;\n+\t\tstruct diff_filespec one, a, b;\n \n \t\tif (!o_sha) {\n \t\t\treason = \"add/add\";\n \t\t\to_sha = (unsigned char *)null_sha1;\n \t\t}\n-\t\toutput(2, \"Auto-merged %s\", path);\n-\t\to.path = a.path = b.path = (char *)path;\n-\t\thashcpy(o.sha1, o_sha);\n-\t\to.mode = o_mode;\n+\t\toutput(o, 2, \"Auto-merged %s\", path);\n+\t\tone.path = a.path = b.path = (char *)path;\n+\t\thashcpy(one.sha1, o_sha);\n+\t\tone.mode = o_mode;\n \t\thashcpy(a.sha1, a_sha);\n \t\ta.mode = a_mode;\n \t\thashcpy(b.sha1, b_sha);\n \t\tb.mode = b_mode;\n \n-\t\tmfi = merge_file(&o, &a, &b,\n-\t\t\t\t branch1, branch2);\n+\t\tmfi = merge_file(&one, &a, &b,\n+\t\t\t\t o->branch1, o->branch2);\n \n \t\tclean_merge = mfi.clean;\n \t\tif (mfi.clean)\n \t\t\tupdate_file(1, mfi.sha, mfi.mode, path);\n \t\telse if (S_ISGITLINK(mfi.mode))\n-\t\t\toutput(1, \"CONFLICT (submodule): Merge conflict in %s \"\n+\t\t\toutput(o, 1, \"CONFLICT (submodule): Merge conflict in %s \"\n \t\t\t       \"- needs %s\", path, sha1_to_hex(b.sha1));\n \t\telse {\n-\t\t\toutput(1, \"CONFLICT (%s): Merge conflict in %s\",\n+\t\t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n \t\t\t\t\treason, path);\n \n \t\t\tif (index_only)\n@@ -1146,11 +1143,10 @@ static int process_entry(const char *path, struct stage_data *entry,\n \treturn clean_merge;\n }\n \n-int merge_trees(struct tree *head,\n+int merge_trees(struct merge_options *o,\n+\t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *common,\n-\t\tconst char *branch1,\n-\t\tconst char *branch2,\n \t\tstruct tree **result)\n {\n \tint code, clean;\n@@ -1161,7 +1157,7 @@ int merge_trees(struct tree *head,\n \t}\n \n \tif (sha_eq(common->object.sha1, merge->object.sha1)) {\n-\t\toutput(0, \"Already uptodate!\");\n+\t\toutput(o, 0, \"Already uptodate!\");\n \t\t*result = head;\n \t\treturn 1;\n \t}\n@@ -1182,15 +1178,14 @@ int merge_trees(struct tree *head,\n \t\tget_files_dirs(merge);\n \n \t\tentries = get_unmerged();\n-\t\tre_head  = get_renames(head, common, head, merge, entries);\n-\t\tre_merge = get_renames(merge, common, head, merge, entries);\n-\t\tclean = process_renames(re_head, re_merge,\n-\t\t\t\tbranch1, branch2);\n+\t\tre_head  = get_renames(o, head, common, head, merge, entries);\n+\t\tre_merge = get_renames(o, merge, common, head, merge, entries);\n+\t\tclean = process_renames(o, re_head, re_merge);\n \t\tfor (i = 0; i < entries->nr; i++) {\n \t\t\tconst char *path = entries->items[i].string;\n \t\t\tstruct stage_data *e = entries->items[i].util;\n \t\t\tif (!e->processed\n-\t\t\t\t&& !process_entry(path, e, branch1, branch2))\n+\t\t\t\t&& !process_entry(o, path, e))\n \t\t\t\tclean = 0;\n \t\t}\n \n@@ -1203,7 +1198,7 @@ int merge_trees(struct tree *head,\n \t\tclean = 1;\n \n \tif (index_only)\n-\t\t*result = write_tree_from_memory();\n+\t\t*result = write_tree_from_memory(o);\n \n \treturn clean;\n }\n@@ -1223,10 +1218,9 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n  * Merge the commits h1 and h2, return the resulting virtual\n  * commit object and a flag indicating the cleanness of the merge.\n  */\n-int merge_recursive(struct commit *h1,\n+int merge_recursive(struct merge_options *o,\n+\t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    const char *branch1,\n-\t\t    const char *branch2,\n \t\t    struct commit_list *ca,\n \t\t    struct commit **result)\n {\n@@ -1235,8 +1229,8 @@ int merge_recursive(struct commit *h1,\n \tstruct tree *mrtree = mrtree;\n \tint clean;\n \n-\tif (show(4)) {\n-\t\toutput(4, \"Merging:\");\n+\tif (show(o, 4)) {\n+\t\toutput(o, 4, \"Merging:\");\n \t\toutput_commit_title(h1);\n \t\toutput_commit_title(h2);\n \t}\n@@ -1246,8 +1240,8 @@ int merge_recursive(struct commit *h1,\n \t\tca = reverse_commit_list(ca);\n \t}\n \n-\tif (show(5)) {\n-\t\toutput(5, \"found %u common ancestor(s):\", commit_list_count(ca));\n+\tif (show(o, 5)) {\n+\t\toutput(o, 5, \"found %u common ancestor(s):\", commit_list_count(ca));\n \t\tfor (iter = ca; iter; iter = iter->next)\n \t\t\toutput_commit_title(iter->item);\n \t}\n@@ -1264,6 +1258,7 @@ int merge_recursive(struct commit *h1,\n \t}\n \n \tfor (iter = ca; iter; iter = iter->next) {\n+\t\tconst char *saved_b1, *saved_b2;\n \t\tcall_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n@@ -1273,11 +1268,14 @@ int merge_recursive(struct commit *h1,\n \t\t * \"conflicts\" were already resolved.\n \t\t */\n \t\tdiscard_cache();\n-\t\tmerge_recursive(merged_common_ancestors, iter->item,\n-\t\t\t\t\"Temporary merge branch 1\",\n-\t\t\t\t\"Temporary merge branch 2\",\n-\t\t\t\tNULL,\n-\t\t\t\t&merged_common_ancestors);\n+\t\tsaved_b1 = o->branch1;\n+\t\tsaved_b2 = o->branch2;\n+\t\to->branch1 = \"Temporary merge branch 1\";\n+\t\to->branch2 = \"Temporary merge branch 2\";\n+\t\tmerge_recursive(o, merged_common_ancestors, iter->item,\n+\t\t\t\tNULL, &merged_common_ancestors);\n+\t\to->branch1 = saved_b1;\n+\t\to->branch2 = saved_b2;\n \t\tcall_depth--;\n \n \t\tif (!merged_common_ancestors)\n@@ -1291,8 +1289,8 @@ int merge_recursive(struct commit *h1,\n \t} else\n \t\tindex_only = 1;\n \n-\tclean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,\n-\t\t\t    branch1, branch2, &mrtree);\n+\tclean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,\n+\t\t\t    &mrtree);\n \n \tif (index_only) {\n \t\t*result = make_virtual_commit(mrtree, \"merged tree\");\n@@ -1319,35 +1317,33 @@ static struct commit *get_ref(const unsigned char *sha1, const char *name)\n \treturn (struct commit *)object;\n }\n \n-int merge_recursive_generic(const char **base_list,\n-\t\tconst unsigned char *head_sha1, const char *head_name,\n-\t\tconst unsigned char *next_sha1, const char *next_name)\n+int merge_recursive_generic(struct merge_options *o,\n+\t\t\t    const unsigned char *head,\n+\t\t\t    const unsigned char *merge,\n+\t\t\t    int num_base_list,\n+\t\t\t    const unsigned char **base_list,\n+\t\t\t    struct commit **result)\n {\n \tint clean, index_fd;\n \tstruct lock_file *lock = xcalloc(1, sizeof(struct lock_file));\n-\tstruct commit *result;\n-\tstruct commit *head_commit = get_ref(head_sha1, head_name);\n-\tstruct commit *next_commit = get_ref(next_sha1, next_name);\n+\tstruct commit *head_commit = get_ref(head, o->branch1);\n+\tstruct commit *next_commit = get_ref(merge, o->branch2);\n \tstruct commit_list *ca = NULL;\n \n \tif (base_list) {\n \t\tint i;\n-\t\tfor (i = 0; base_list[i]; ++i) {\n-\t\t\tunsigned char sha[20];\n+\t\tfor (i = 0; i < num_base_list; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (get_sha1(base_list[i], sha))\n-\t\t\t\treturn error(\"Could not resolve ref '%s'\",\n-\t\t\t\t\t\t\t\tbase_list[i]);\n-\t\t\tif (!(base = get_ref(sha, base_list[i])))\n+\t\t\tif (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))\n \t\t\t\treturn error(\"Could not parse object '%s'\",\n-\t\t\t\t\t\t\t\tbase_list[i]);\n+\t\t\t\t\tsha1_to_hex(base_list[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t}\n \n \tindex_fd = hold_locked_index(lock, 1);\n-\tclean = merge_recursive(head_commit, next_commit,\n-\t\t\t\thead_name, next_name, ca, &result);\n+\tclean = merge_recursive(o, head_commit, next_commit, ca,\n+\t\t\tresult);\n \tif (active_cache_changed &&\n \t\t\t(write_cache(index_fd, active_cache, active_nr) ||\n \t\t\t commit_locked_index(lock)))\n@@ -1356,29 +1352,35 @@ int merge_recursive_generic(const char **base_list,\n \treturn clean ? 0 : 1;\n }\n \n-int merge_recursive_config(const char *var, const char *value, void *cb)\n+static int merge_recursive_config(const char *var, const char *value, void *cb)\n {\n+\tstruct merge_options *o = cb;\n \tif (!strcasecmp(var, \"merge.verbosity\")) {\n-\t\tmerge_recursive_verbosity = git_config_int(var, value);\n+\t\to->verbosity = git_config_int(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcasecmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit = git_config_int(var, value);\n+\t\to->diff_rename_limit = git_config_int(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcasecmp(var, \"merge.renamelimit\")) {\n-\t\tmerge_rename_limit = git_config_int(var, value);\n+\t\to->merge_rename_limit = git_config_int(var, value);\n \t\treturn 0;\n \t}\n \treturn git_default_config(var, value, cb);\n }\n \n-void merge_recursive_setup(int is_subtree_merge)\n+void init_merge_options(struct merge_options *o)\n {\n+\tmemset(o, 0, sizeof(struct merge_options));\n+\to->verbosity = 2;\n+\to->buffer_output = 1;\n+\to->diff_rename_limit = -1;\n+\to->merge_rename_limit = -1;\n+\tgit_config(merge_recursive_config, o);\n \tif (getenv(\"GIT_MERGE_VERBOSITY\"))\n-\t\tmerge_recursive_verbosity =\n+\t\to->verbosity =\n \t\t\tstrtol(getenv(\"GIT_MERGE_VERBOSITY\"), NULL, 10);\n-\tif (merge_recursive_verbosity >= 5)\n-\t\tbuffer_output = 0;\n-\tsubtree_merge = is_subtree_merge;\n+\tif (o->verbosity >= 5)\n+\t\to->buffer_output = 0;\n }\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 4dd6476..72f0a28 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -1,26 +1,42 @@\n #ifndef MERGE_RECURSIVE_H\n #define MERGE_RECURSIVE_H\n \n-int merge_recursive(struct commit *h1,\n+struct merge_options {\n+\tconst char *branch1;\n+\tconst char *branch2;\n+\tunsigned subtree_merge : 1;\n+\tunsigned buffer_output : 1;\n+\tint verbosity;\n+\tint diff_rename_limit;\n+\tint merge_rename_limit;\n+};\n+\n+/* merge_trees() but with recursive ancestor consolidation */\n+int merge_recursive(struct merge_options *o,\n+\t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    const char *branch1,\n-\t\t    const char *branch2,\n \t\t    struct commit_list *ancestors,\n \t\t    struct commit **result);\n \n-int merge_trees(struct tree *head,\n+/* rename-detecting three-way merge, no recursion */\n+int merge_trees(struct merge_options *o,\n+\t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *common,\n-\t\tconst char *branch1,\n-\t\tconst char *branch2,\n \t\tstruct tree **result);\n-extern int merge_recursive_generic(const char **base_list,\n-\t\tconst unsigned char *head_sha1, const char *head_name,\n-\t\tconst unsigned char *next_sha1, const char *next_name);\n-int merge_recursive_config(const char *var, const char *value, void *cb);\n-void merge_recursive_setup(int is_subtree_merge);\n-struct tree *write_tree_from_memory(void);\n \n-extern int merge_recursive_verbosity;\n+/*\n+ * \"git-merge-recursive\" can be fed trees; wrap them into\n+ * virtual commits and call merge_recursive() proper.\n+ */\n+int merge_recursive_generic(struct merge_options *o,\n+\t\t\t    const unsigned char *head,\n+\t\t\t    const unsigned char *merge,\n+\t\t\t    int num_ca,\n+\t\t\t    const unsigned char **ca,\n+\t\t\t    struct commit **result);\n+\n+void init_merge_options(struct merge_options *o);\n+struct tree *write_tree_from_memory(struct merge_options *o);\n \n #endif\n-- \n1.6.0.rc3.17.gc14c8.dirty\n"},{"id":"88869","messageId":"7v8wuhiw05.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"1219674357-31000-1-git-send-email-vmiklos@frugalware.org","subject":"Re: [PATCH] merge-recursive: introduce merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-28T04:50:02Z","receivedAt":"2008-08-28T04:50:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> On Sun, Aug 24, 2008 at 11:06:06PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n>> Miklos Vajna <vmiklos@frugalware.org> writes:\n>>\n>> > 1) This applies on top of 1c868d4 (merge-recursive.c: Add more\n>> > generic merge_recursive_generic()). I can rebase this (along with\n>> > 1c868d4 and 1c868d4^) on top of current master, if this is a problem.\n>>\n>> It probably is cleaner to treat this as a fresh topic from scratch on\n>> top of 'master', as we do not have anything outstanding in 'next'\n>> around this area.\n>\n> I'm now confused about what should I do:\n>\n> 1) Nothing. (That's what I did for now.)\n>\n> 2) Rebase against master and resend.\n>\n> 3) Rebase, squash and resend.\n\nWhat I meant was that the final state after applying this patch may make\nwhat \"git log master..1c868d4\" currently shows (there are two patches if I\nrecall correctly) an incomplete failed experiment, in which case squashing\nand possibly refactoring (if the result of squashing is too messy) would\nmake the history easier to review.\n\nBut I looked at the series again after rebasing them myself.\n\nIf you want to clean-it-up, you could replace them by sending in updates\nto refactor them.  I think they are still ugly, even though the end result\nis tolerable ;-)\n"},{"id":"89213","messageId":"1220110929-6803-1-git-send-email-vmiklos@frugalware.org","threadId":"15081","inReplyTo":"1219674357-31000-1-git-send-email-vmiklos@frugalware.org","subject":"[PATCH] merge-recursive: fix subtree merge","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-30T15:42:09Z","receivedAt":"2008-08-30T15:42:09Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"We should decide if we do a subtree merge based on the merge_options\nstruct, not based on the global variable, which should not even exist\nat all.\n\nSigned-off-by: Miklos Vajna <vmiklos@frugalware.org>\n---\n\nOn Mon, Aug 25, 2008 at 04:25:57PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:\n> It also takes care of subtree merge, output buffering, verbosity, and\n> rename limits - these were global variables till now in\n> merge-recursive.c.\n\nActually subtree_merge was not used from the struct merge_options, here\nis the fix.\n\n merge-recursive.c |    4 +---\n 1 files changed, 1 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3a38cc6..457ad84 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -20,8 +20,6 @@\n #include \"attr.h\"\n #include \"merge-recursive.h\"\n \n-static int subtree_merge;\n-\n static struct tree *shift_tree_object(struct tree *one, struct tree *two)\n {\n \tunsigned char shifted[20];\n@@ -1152,7 +1150,7 @@ int merge_trees(struct merge_options *o,\n {\n \tint code, clean;\n \n-\tif (subtree_merge) {\n+\tif (o->subtree_merge) {\n \t\tmerge = shift_tree_object(head, merge);\n \t\tcommon = shift_tree_object(head, common);\n \t}\n-- \n1.6.0\n"},{"id":"89217","messageId":"7vbpza1mpi.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"1220110929-6803-1-git-send-email-vmiklos@frugalware.org","subject":"Re: [PATCH] merge-recursive: fix subtree merge","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-30T16:39:37Z","receivedAt":"2008-08-30T16:39:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> On Mon, Aug 25, 2008 at 04:25:57PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:\n>> It also takes care of subtree merge, output buffering, verbosity, and\n>> rename limits - these were global variables till now in\n>> merge-recursive.c.\n>\n> Actually subtree_merge was not used from the struct merge_options, here\n> is the fix.\n\nAs bd1e8fe (merge-recursive: introduce merge_options, 2008-08-25) is not\npart of any solid integration branch yet, I'll squash this into it.\n\nIn the longer term, I suspect that other file scope static variables in\nmerge-recursive.c, such as call_depth, may want to move to merge_options,\nnot as \"option\" but as \"current state\".  But that is quite minor, as it\nonly affects reentrancy, and I do not see a reason for merge_recursive()\nto be reentrant (yet).\n"},{"id":"89228","messageId":"7vabeuie0j.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"1220110929-6803-1-git-send-email-vmiklos@frugalware.org","subject":"Re: [PATCH] merge-recursive: fix subtree merge","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-30T17:55:24Z","receivedAt":"2008-08-30T17:55:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> On Mon, Aug 25, 2008 at 04:25:57PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:\n>> It also takes care of subtree merge, output buffering, verbosity, and\n>> rename limits - these were global variables till now in\n>> merge-recursive.c.\n>\n> Actually subtree_merge was not used from the struct merge_options, here\n> is the fix.\n\nThis makes me wonder why we didn't see any breakages in the existing\ntests.\n"},{"id":"89347","messageId":"20080831234948.GB16514@genesis.frugalware.org","threadId":"15081","inReplyTo":"7vabeuie0j.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] merge-recursive: fix subtree merge","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-08-31T23:49:48Z","receivedAt":"2008-08-31T23:49:48Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Sat, Aug 30, 2008 at 10:55:24AM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> This makes me wonder why we didn't see any breakages in the existing\n> tests.\n\nThat is a really interesting question. I have no idea how t6029 can pass\nwithout this fix, but actually it does. (Yes, I read that part of the\ncode and the testsuite as well, but still no clue.)\n"},{"id":"89350","messageId":"20080901010612.GC16514@genesis.frugalware.org","threadId":"15081","inReplyTo":"7v3al0zmv8.fsf@gitster.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-09-01T01:06:12Z","receivedAt":"2008-09-01T01:06:12Z","isPatch":false,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Tue, Aug 19, 2008 at 03:00:27PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> and the call chain would become:\n> (...)\n>     cmd_revert(), cmd_am(), cmd_checkout(), cmd_stash(), ...\n>     -> merge_trees()\n\nI tried to let cmd_revert() use merge_trees() only and not\nmerge_recursive_generic(), but something is fishy with it.\n\nt3501-revert-cherry-pick passes fine, but t3404-rebase-interactive fail,\nbecase once we have a conflict, git diff --name-status says 'M' for the\ngiven file and not 'U', which is obviously wrong.\n\nI'm sending both patches: the one that works properly using\nmerge_recursive_generic() - and which one passes the testsuite here, and\nthe one that is elegant as it uses merge_trees(), but actually not\ncomplete, at least regarding not adding the unmerged entries to the\nindex.\n\nSo, first here is my failed attempt to use merge_trees() inside\nbuiltin-revert:\n\ndiff --git a/builtin-revert.c b/builtin-revert.c\nindex 3667705..3071518 100644\n--- a/builtin-revert.c\n+++ b/builtin-revert.c\n@@ -12,6 +12,7 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n+#include \"merge-recursive.h\"\n \n /*\n  * This implements the builtins revert and cherry-pick.\n@@ -201,36 +202,6 @@ static void set_author_ident_env(const char *message)\n \t\t\tsha1_to_hex(commit->object.sha1));\n }\n \n-static int merge_recursive(const char *base_sha1,\n-\t\tconst char *head_sha1, const char *head_name,\n-\t\tconst char *next_sha1, const char *next_name)\n-{\n-\tchar buffer[256];\n-\tconst char *argv[6];\n-\tint i = 0;\n-\n-\tsprintf(buffer, \"GITHEAD_%s\", head_sha1);\n-\tsetenv(buffer, head_name, 1);\n-\tsprintf(buffer, \"GITHEAD_%s\", next_sha1);\n-\tsetenv(buffer, next_name, 1);\n-\n-\t/*\n-\t * This three way merge is an interesting one.  We are at\n-\t * $head, and would want to apply the change between $commit\n-\t * and $prev on top of us (when reverting), or the change between\n-\t * $prev and $commit on top of us (when cherry-picking or replaying).\n-\t */\n-\targv[i++] = \"merge-recursive\";\n-\tif (base_sha1)\n-\t\targv[i++] = base_sha1;\n-\targv[i++] = \"--\";\n-\targv[i++] = head_sha1;\n-\targv[i++] = next_sha1;\n-\targv[i++] = NULL;\n-\n-\treturn run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);\n-}\n-\n static char *help_msg(const unsigned char *sha1)\n {\n \tstatic char helpbuf[1024];\n@@ -271,6 +242,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \tchar *oneline, *reencoded_message = NULL;\n \tconst char *message, *encoding;\n \tconst char *defmsg = xstrdup(git_path(\"MERGE_MSG\"));\n+\tstruct merge_options o;\n+\tstruct tree *result;\n \n \tgit_config(git_default_config, NULL);\n \tme = action == REVERT ? \"revert\" : \"cherry-pick\";\n@@ -374,13 +347,16 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t\t}\n \t}\n \n-\tif (merge_recursive(base == NULL ?\n-\t\t\t\tNULL : sha1_to_hex(base->object.sha1),\n-\t\t\t\tsha1_to_hex(head), \"HEAD\",\n-\t\t\t\tsha1_to_hex(next->object.sha1), oneline) ||\n-\t\t\twrite_cache_as_tree(head, 0, NULL)) {\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = oneline;\n+\tparse_commit(next);\n+\tparse_commit(base);\n+\tif (!merge_trees(&o, lookup_commit_reference_gently(head, 0)->tree,\n+\t\t\t\tnext->tree, base->tree, &result) ||\n+\t\twrite_cache_as_tree(head, 0, NULL)) {\n \t\tadd_to_msg(\"\\nConflicts:\\n\\n\");\n-\t\tread_cache();\n \t\tfor (i = 0; i < active_nr;) {\n \t\t\tstruct cache_entry *ce = active_cache[i++];\n \t\t\tif (ce_stage(ce)) {\n"},{"id":"89351","messageId":"1220231341-1748-1-git-send-email-vmiklos@frugalware.org","threadId":"15081","inReplyTo":"20080901010612.GC16514@genesis.frugalware.org","subject":"[PATCH] builtin-revert: use merge_recursive_generic()","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-09-01T01:09:01Z","receivedAt":"2008-09-01T01:09:01Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"We had a separate function to run merge-recursive in a separate process,\nbut this is not really necessary, since we have\nmerge_recursive_generic() to do the same, without wasting resources.\n\nSigned-off-by: Miklos Vajna <vmiklos@frugalware.org>\n---\n\nOn Mon, Sep 01, 2008 at 03:06:12AM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:\n> I'm sending both patches: the one that works properly using\n> merge_recursive_generic() - and which one passes the testsuite here\n\nHere it is.\n\n builtin-revert.c |   55 ++++++++++++++++++-----------------------------------\n 1 files changed, 19 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin-revert.c b/builtin-revert.c\nindex 3667705..8d4ebcf 100644\n--- a/builtin-revert.c\n+++ b/builtin-revert.c\n@@ -12,6 +12,7 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n+#include \"merge-recursive.h\"\n \n /*\n  * This implements the builtins revert and cherry-pick.\n@@ -201,36 +202,6 @@ static void set_author_ident_env(const char *message)\n \t\t\tsha1_to_hex(commit->object.sha1));\n }\n \n-static int merge_recursive(const char *base_sha1,\n-\t\tconst char *head_sha1, const char *head_name,\n-\t\tconst char *next_sha1, const char *next_name)\n-{\n-\tchar buffer[256];\n-\tconst char *argv[6];\n-\tint i = 0;\n-\n-\tsprintf(buffer, \"GITHEAD_%s\", head_sha1);\n-\tsetenv(buffer, head_name, 1);\n-\tsprintf(buffer, \"GITHEAD_%s\", next_sha1);\n-\tsetenv(buffer, next_name, 1);\n-\n-\t/*\n-\t * This three way merge is an interesting one.  We are at\n-\t * $head, and would want to apply the change between $commit\n-\t * and $prev on top of us (when reverting), or the change between\n-\t * $prev and $commit on top of us (when cherry-picking or replaying).\n-\t */\n-\targv[i++] = \"merge-recursive\";\n-\tif (base_sha1)\n-\t\targv[i++] = base_sha1;\n-\targv[i++] = \"--\";\n-\targv[i++] = head_sha1;\n-\targv[i++] = next_sha1;\n-\targv[i++] = NULL;\n-\n-\treturn run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);\n-}\n-\n static char *help_msg(const unsigned char *sha1)\n {\n \tstatic char helpbuf[1024];\n@@ -266,12 +237,16 @@ static int index_is_dirty(void)\n static int revert_or_cherry_pick(int argc, const char **argv)\n {\n \tunsigned char head[20];\n+\tunsigned const char *ca = NULL;\n \tstruct commit *base, *next, *parent;\n-\tint i;\n+\tint i, fail;\n \tchar *oneline, *reencoded_message = NULL;\n \tconst char *message, *encoding;\n \tconst char *defmsg = xstrdup(git_path(\"MERGE_MSG\"));\n+\tstruct merge_options o;\n+\tstruct commit *result;\n \n+\tinit_merge_options(&o);\n \tgit_config(git_default_config, NULL);\n \tme = action == REVERT ? \"revert\" : \"cherry-pick\";\n \tsetenv(GIT_REFLOG_ACTION, me, 0);\n@@ -374,11 +349,19 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t\t}\n \t}\n \n-\tif (merge_recursive(base == NULL ?\n-\t\t\t\tNULL : sha1_to_hex(base->object.sha1),\n-\t\t\t\tsha1_to_hex(head), \"HEAD\",\n-\t\t\t\tsha1_to_hex(next->object.sha1), oneline) ||\n-\t\t\twrite_cache_as_tree(head, 0, NULL)) {\n+\tif (base)\n+\t\tca = base->object.sha1;\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = oneline;\n+\tfail = merge_recursive_generic(&o,\n+\t\t\t\thead,\n+\t\t\t\tnext->object.sha1,\n+\t\t\t\tca ? 1 : 0,\n+\t\t\t\t&ca,\n+\t\t\t\t&result);\n+\tif (fail < 0)\n+\t\texit(1);\n+\tif (fail || write_cache_as_tree(head, 0, NULL)) {\n \t\tadd_to_msg(\"\\nConflicts:\\n\\n\");\n \t\tread_cache();\n \t\tfor (i = 0; i < active_nr;) {\n-- \n1.6.0\n"},{"id":"89531","messageId":"7v1w02l3jc.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"20080901010612.GC16514@genesis.frugalware.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-09-02T20:02:31Z","receivedAt":"2008-09-02T20:02:31Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> I tried to let cmd_revert() use merge_trees() only and not\n> merge_recursive_generic(), but something is fishy with it.\n\n> t3501-revert-cherry-pick passes fine, but t3404-rebase-interactive fail,\n> becase once we have a conflict, git diff --name-status says 'M' for the\n> given file and not 'U', which is obviously wrong.\n\nI think this is because you are forgotting to write the index file out.\nYou are calling \"git commit -n\" at the end if a cherry-pick/revert is\nclean, or giving the user back an unmerged state in the index otherwise.\nIn either case, after merge_trees() gives you a potentially unmerged index\nback in-core, you need to write it to the $GIT_INDEX_FILE for later users.\n\nAlso notice that your output is awfully silent.  You are forgetting to\nflush the buffered output that is kept in \"obuf\".\n\nPlease check what other extra things merge_recursive() function does that\nmerge_trees() doesn't; the above two are what I've spotted by code\ninspection, but there may be others.  It could be an indication of\nincorrect interface layering when the builtin was refactored.\n\nI found it a bit disturbing that \"index_only\" and \"call_depth\" were not\npart of merge_options structure.  The machinery is not yet meant to be\nreentrant, so we may rely the previous call to the function (either\nmerge_recursive() or merge_trees() revert them to a sane initial value,\nbut it is a bit unnerving, having to depend on that assumption.\n\nI think your code happily dereferences NULL when picking the root commit\nis involved, although I do not think the failure in the test suite you saw\nis related to this omission.\n\nHere is a partial fix to address the above issues I noticed on\ntop of your version; untested.\n\n builtin-revert.c |   46 ++++++++++++++++++++++++++++++++++++----------\n 1 files changed, 36 insertions(+), 10 deletions(-)\n\ndiff --git i/builtin-revert.c w/builtin-revert.c\nindex 3071518..41f3ca2 100644\n--- i/builtin-revert.c\n+++ w/builtin-revert.c\n@@ -234,16 +234,27 @@ static int index_is_dirty(void)\n \treturn !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);\n }\n \n+static struct tree *empty_tree(void)\n+{\n+\tstruct tree *tree = xcalloc(1, sizeof(struct tree));\n+\n+\ttree->object.parsed = 1;\n+\ttree->object.type = OBJ_TREE;\n+\tpretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);\n+\treturn tree;\n+}\n+\n static int revert_or_cherry_pick(int argc, const char **argv)\n {\n \tunsigned char head[20];\n \tstruct commit *base, *next, *parent;\n-\tint i;\n+\tint i, index_fd, clean;\n \tchar *oneline, *reencoded_message = NULL;\n \tconst char *message, *encoding;\n \tconst char *defmsg = xstrdup(git_path(\"MERGE_MSG\"));\n \tstruct merge_options o;\n-\tstruct tree *result;\n+\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n+\tstatic struct lock_file index_lock;\n \n \tgit_config(git_default_config, NULL);\n \tme = action == REVERT ? \"revert\" : \"cherry-pick\";\n@@ -254,6 +265,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \tif (action == REVERT && !no_replay)\n \t\tdie(\"revert is incompatible with replay\");\n \n+\tindex_fd = hold_locked_index(&index_lock, 1);\n+\n+\tif (read_cache() < 0)\n+\t\tdie(\"git %s: failed to read the index\", me);\n \tif (no_commit) {\n \t\t/*\n \t\t * We do not intend to commit immediately.  We just want to\n@@ -266,12 +281,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t} else {\n \t\tif (get_sha1(\"HEAD\", head))\n \t\t\tdie (\"You do not have a valid HEAD\");\n-\t\tif (read_cache() < 0)\n-\t\t\tdie(\"could not read the index\");\n \t\tif (index_is_dirty())\n \t\t\tdie (\"Dirty index: cannot %s\", me);\n-\t\tdiscard_cache();\n \t}\n+\tdiscard_cache();\n \n \tif (!commit->parents) {\n \t\tif (action == REVERT)\n@@ -305,6 +318,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t\tdie (\"Cannot get commit message for %s\",\n \t\t\t\tsha1_to_hex(commit->object.sha1));\n \n+\tif (parent && parse_commit(parent) < 0)\n+\t\tdie(\"%s: cannot parse parent commit %s\",\n+\t\t    me, sha1_to_hex(parent->object.sha1));\n+\n \t/*\n \t * \"commit\" is an existing commit.  We would want to apply\n \t * the difference it introduces since its first parent \"prev\"\n@@ -351,11 +368,20 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \tinit_merge_options(&o);\n \to.branch1 = \"HEAD\";\n \to.branch2 = oneline;\n-\tparse_commit(next);\n-\tparse_commit(base);\n-\tif (!merge_trees(&o, lookup_commit_reference_gently(head, 0)->tree,\n-\t\t\t\tnext->tree, base->tree, &result) ||\n-\t\twrite_cache_as_tree(head, 0, NULL)) {\n+\n+\thead_tree = parse_tree_indirect(head);\n+\tnext_tree = next ? next->tree : empty_tree();\n+\tbase_tree = base ? base->tree : empty_tree();\n+\n+\tclean = merge_trees(&o,\n+\t\t\t    head_tree,\n+\t\t\t    next_tree, base_tree, &result);\n+\n+\tif (write_cache(index_fd, active_cache, active_nr) ||\n+\t    commit_locked_index(&index_lock))\n+\t\tdie(\"%s: Unable to write new index file\", me);\n+\n+\tif (!clean) {\n \t\tadd_to_msg(\"\\nConflicts:\\n\\n\");\n \t\tfor (i = 0; i < active_nr;) {\n \t\t\tstruct cache_entry *ce = active_cache[i++];\n"},{"id":"89542","messageId":"7vfxoijn2l.fsf@gitster.siamese.dyndns.org","threadId":"15081","inReplyTo":"7v1w02l3jc.fsf@gitster.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Aug 2008, #05; Tue, 19)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-09-02T20:43:30Z","receivedAt":"2008-09-02T20:43:30Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Miklos Vajna <vmiklos@frugalware.org> writes:\n>\n>> I tried to let cmd_revert() use merge_trees() only and not\n>> merge_recursive_generic(), but something is fishy with it.\n> Here is a partial fix to address the above issues I noticed on\n> top of your version; untested.\n\nThis has a few fix-ups in addition to the one I sent earlier (not\nincremental, this applies directly on top of yours, bypassing the earlier\none), and has passed the self tests.\n\n builtin-revert.c |   47 +++++++++++++++++++++++++++++++++++++----------\n 1 files changed, 37 insertions(+), 10 deletions(-)\n\ndiff --git i/builtin-revert.c w/builtin-revert.c\nindex 3071518..8486539 100644\n--- i/builtin-revert.c\n+++ w/builtin-revert.c\n@@ -234,16 +234,27 @@ static int index_is_dirty(void)\n \treturn !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);\n }\n \n+static struct tree *empty_tree(void)\n+{\n+\tstruct tree *tree = xcalloc(1, sizeof(struct tree));\n+\n+\ttree->object.parsed = 1;\n+\ttree->object.type = OBJ_TREE;\n+\tpretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);\n+\treturn tree;\n+}\n+\n static int revert_or_cherry_pick(int argc, const char **argv)\n {\n \tunsigned char head[20];\n \tstruct commit *base, *next, *parent;\n-\tint i;\n+\tint i, index_fd, clean;\n \tchar *oneline, *reencoded_message = NULL;\n \tconst char *message, *encoding;\n \tconst char *defmsg = xstrdup(git_path(\"MERGE_MSG\"));\n \tstruct merge_options o;\n-\tstruct tree *result;\n+\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n+\tstatic struct lock_file index_lock;\n \n \tgit_config(git_default_config, NULL);\n \tme = action == REVERT ? \"revert\" : \"cherry-pick\";\n@@ -254,6 +265,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \tif (action == REVERT && !no_replay)\n \t\tdie(\"revert is incompatible with replay\");\n \n+\tif (read_cache() < 0)\n+\t\tdie(\"git %s: failed to read the index\", me);\n \tif (no_commit) {\n \t\t/*\n \t\t * We do not intend to commit immediately.  We just want to\n@@ -266,12 +279,12 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t} else {\n \t\tif (get_sha1(\"HEAD\", head))\n \t\t\tdie (\"You do not have a valid HEAD\");\n-\t\tif (read_cache() < 0)\n-\t\t\tdie(\"could not read the index\");\n \t\tif (index_is_dirty())\n \t\t\tdie (\"Dirty index: cannot %s\", me);\n-\t\tdiscard_cache();\n \t}\n+\tdiscard_cache();\n+\n+\tindex_fd = hold_locked_index(&index_lock, 1);\n \n \tif (!commit->parents) {\n \t\tif (action == REVERT)\n@@ -305,6 +318,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t\tdie (\"Cannot get commit message for %s\",\n \t\t\t\tsha1_to_hex(commit->object.sha1));\n \n+\tif (parent && parse_commit(parent) < 0)\n+\t\tdie(\"%s: cannot parse parent commit %s\",\n+\t\t    me, sha1_to_hex(parent->object.sha1));\n+\n \t/*\n \t * \"commit\" is an existing commit.  We would want to apply\n \t * the difference it introduces since its first parent \"prev\"\n@@ -351,11 +368,21 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \tinit_merge_options(&o);\n \to.branch1 = \"HEAD\";\n \to.branch2 = oneline;\n-\tparse_commit(next);\n-\tparse_commit(base);\n-\tif (!merge_trees(&o, lookup_commit_reference_gently(head, 0)->tree,\n-\t\t\t\tnext->tree, base->tree, &result) ||\n-\t\twrite_cache_as_tree(head, 0, NULL)) {\n+\n+\thead_tree = parse_tree_indirect(head);\n+\tnext_tree = next ? next->tree : empty_tree();\n+\tbase_tree = base ? base->tree : empty_tree();\n+\n+\tclean = merge_trees(&o,\n+\t\t\t    head_tree,\n+\t\t\t    next_tree, base_tree, &result);\n+\n+\tif (active_cache_changed &&\n+\t    (write_cache(index_fd, active_cache, active_nr) ||\n+\t     commit_locked_index(&index_lock)))\n+\t\tdie(\"%s: Unable to write new index file\", me);\n+\n+\tif (!clean) {\n \t\tadd_to_msg(\"\\nConflicts:\\n\\n\");\n \t\tfor (i = 0; i < active_nr;) {\n \t\t\tstruct cache_entry *ce = active_cache[i++];\n"}]}