git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/9] Use merge_recursive() directly in the builtin am
@ 2016-06-29 11:36 Johannes Schindelin
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
                   ` (9 more replies)
  0 siblings, 10 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:36 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

This is the long-awaited re-roll of the attempt to avoid spawning
merge-recursive from the builtin am and use merge_recursive() directly
instead.

As indicated in the message of the final commit, the performance
improvement is modest, if noticable.

The *real* reason for the reroll is that I need a libified recursive
merge to accelerate the interactive rebase by teaching the sequencer to
do rebase -i's grunt work.

In other words, this is one of those 13 (now 14) patch series leading up
to a faster interactive rebase.

It did take quite a while to go through the code "with a fine-toothed
comb", and it did turn up a few surprises.

For example, the recursive merge calls remove_file() a couple of times
but actually does not always care about the return value, and rightfully
so. When updating a working tree, for example, where a file was
replaced by a directory, the code may create the directory first
(implicitly deleting the file) and only then attempt to remove the file,
failing because it is a directory already.

One might argue that this logic is flawed, then, and I would agree.
Right now my focus is on rebase -i and I want to avoid getting
side-tracked by the recursive merge logic. So the clean-up will need to
wait for another day (or week).

We also need to be extra careful to retain backwards-compatibility. The
test script t6022-merge-rename.sh, for example, verifies that `git pull`
exits with status 128 in case of a fatal error. To that end, we need to
make sure that fatal errors are handled by existing (builtin) users via
exit(128) (or die(), which calls exit(128) at the end). New users (such
as a builtin helper doing rebase -i's grunt work) may want to print some
helpful advice what happened and how to get out of this mess before
erroring out.

In contrast to the original attempt at libifying merge_recursive() (as
part of fixing a regression in the builtin am which wanted to print some
advice, but could not, because the merge machinery die()d before it
could), I no longer use the "gently" flag. Better to get it right to
begin with: fatal errors are indicated by a negative return value. No
dying without proper cause.

In an earlier iteration of this patch series which was not sent to the
mailing list, I used the special return vale -128 to indicate "real
fatal errors". This turned out to be unnecessary: returning -1 always,
to indicate that the operation could not complete successfully, is the
appropriate way to handle all errors.

As this patch series touches rather important code, I would really
appreciate thorough reviews, with a particular focus on what regressions
this patch series might introduce.


Johannes Schindelin (8):
  Report bugs consistently
  merge-recursive: clarify code in was_tracked()
  Prepare the builtins for a libified merge_recursive()
  merge_recursive: abort properly upon errors
  merge-recursive: avoid returning a wholesale struct
  merge-recursive: allow write_tree_from_memory() to error out
  merge-recursive: handle return values indicating errors
  merge-recursive: switch to returning errors instead of dying

Junio C Hamano (1):
  am: make a direct call to merge_recursive

 builtin/am.c           |  27 ++--
 builtin/checkout.c     |   4 +-
 builtin/ls-files.c     |   3 +-
 builtin/merge.c        |   4 +
 builtin/update-index.c |   2 +-
 grep.c                 |   8 +-
 imap-send.c            |   2 +-
 merge-recursive.c      | 397 ++++++++++++++++++++++++++++++-------------------
 sequencer.c            |   4 +
 sha1_file.c            |   4 +-
 trailer.c              |   2 +-
 transport.c            |   2 +-
 wt-status.c            |   4 +-
 13 files changed, 279 insertions(+), 184 deletions(-)

Published-As: https://github.com/dscho/git/releases/tag/am-3-merge-recursive-direct-v1
-- 
2.9.0.268.gcabc8b0

base-commit: cf4c2cfe52be5bd973a4838f73a35d3959ce2f43

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH 1/9] Report bugs consistently
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
@ 2016-06-29 11:36 ` Johannes Schindelin
  2016-06-29 15:11   ` Johannes Schindelin
                     ` (4 more replies)
  2016-06-29 11:36 ` [PATCH 2/9] merge-recursive: clarify code in was_tracked() Johannes Schindelin
                   ` (8 subsequent siblings)
  9 siblings, 5 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:36 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

The vast majority of error messages in Git's source code which report a
bug use the convention to prefix the message with "BUG:".

As part of cleaning up merge-recursive to stop die()ing except in case of
detected bugs, let's just make the remainder of the bug reports consistent
with the de facto rule.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/ls-files.c     |  3 ++-
 builtin/update-index.c |  2 +-
 grep.c                 |  8 ++++----
 imap-send.c            |  2 +-
 merge-recursive.c      | 13 ++++++-------
 sha1_file.c            |  4 ++--
 trailer.c              |  2 +-
 transport.c            |  2 +-
 wt-status.c            |  4 ++--
 9 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f02e3d2..00ea91a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
 				 */
 				pos = cache_name_pos(ent->name, ent->len);
 				if (0 <= pos)
-					die("bug in show-killed-files");
+					die("BUG: killed-file %.*s not found",
+						ent->len, ent->name);
 				pos = -pos - 1;
 				while (pos < active_nr &&
 				       ce_stage(active_cache[pos]))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 6cdfd5f..ba04b19 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -1146,7 +1146,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
 		break;
 	default:
-		die("Bug: bad untracked_cache value: %d", untracked_cache);
+		die("BUG: bad untracked_cache value: %d", untracked_cache);
 	}
 
 	if (active_cache_changed) {
diff --git a/grep.c b/grep.c
index 1e15b62..f1ca0a0 100644
--- a/grep.c
+++ b/grep.c
@@ -643,10 +643,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 	for (p = opt->header_list; p; p = p->next) {
 		if (p->token != GREP_PATTERN_HEAD)
-			die("bug: a non-header pattern in grep header list.");
+			die("BUG: a non-header pattern in grep header list.");
 		if (p->field < GREP_HEADER_FIELD_MIN ||
 		    GREP_HEADER_FIELD_MAX <= p->field)
-			die("bug: unknown header field %d", p->field);
+			die("BUG: unknown header field %d", p->field);
 		compile_regexp(p, opt);
 	}
 
@@ -659,7 +659,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 		h = compile_pattern_atom(&pp);
 		if (!h || pp != p->next)
-			die("bug: malformed header expr");
+			die("BUG: malformed header expr");
 		if (!header_group[p->field]) {
 			header_group[p->field] = h;
 			continue;
@@ -1464,7 +1464,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 		case GREP_BINARY_TEXT:
 			break;
 		default:
-			die("bug: unknown binary handling mode");
+			die("BUG: unknown binary handling mode");
 		}
 	}
 
diff --git a/imap-send.c b/imap-send.c
index 938c691..cd39805 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -511,7 +511,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 
 	va_start(va, fmt);
 	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-		die("Fatal: buffer too small. Please report a bug.");
+		die("BUG: buffer too small (%d < %d)", ret, blen);
 	va_end(va);
 	return ret;
 }
diff --git a/merge-recursive.c b/merge-recursive.c
index 65cb5d6..98f4632 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -259,7 +259,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 				fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
 					(int)ce_namelen(ce), ce->name);
 		}
-		die("Bug in merge-recursive.c");
+		die("BUG: unmerged index entries in merge-recursive.c");
 	}
 
 	if (!active_cache_tree)
@@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 
 			if (!sha_eq(a->sha1, b->sha1))
 				result.clean = 0;
-		} else {
-			die(_("unsupported object type in the tree"));
-		}
+		} else
+			die(_("BUG: unsupported object type in the tree"));
 	}
 
 	return result;
@@ -1343,7 +1342,7 @@ static int process_renames(struct merge_options *o,
 			const char *ren2_dst = ren2->pair->two->path;
 			enum rename_type rename_type;
 			if (strcmp(ren1_src, ren2_src) != 0)
-				die("ren1_src != ren2_src");
+				die("BUG: ren1_src != ren2_src");
 			ren2->dst_entry->processed = 1;
 			ren2->processed = 1;
 			if (strcmp(ren1_dst, ren2_dst) != 0) {
@@ -1377,7 +1376,7 @@ static int process_renames(struct merge_options *o,
 			ren2 = lookup->util;
 			ren2_dst = ren2->pair->two->path;
 			if (strcmp(ren1_dst, ren2_dst) != 0)
-				die("ren1_dst != ren2_dst");
+				die("BUG: ren1_dst != ren2_dst");
 
 			clean_merge = 0;
 			ren2->processed = 1;
@@ -1853,7 +1852,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("Unprocessed path??? %s"),
+				die(_("BUG: unprocessed path??? %s"),
 				    entries->items[i].string);
 		}
 
diff --git a/sha1_file.c b/sha1_file.c
index d5e1121..aa7006c 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -795,7 +795,7 @@ void close_all_packs(void)
 
 	for (p = packed_git; p; p = p->next)
 		if (p->do_not_close)
-			die("BUG! Want to close pack marked 'do-not-close'");
+			die("BUG: Want to close pack marked 'do-not-close'");
 		else
 			close_pack(p);
 }
@@ -2330,7 +2330,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
 		if (data)
-			die("BUG in unpack_entry: left loop at a valid delta");
+			die("BUG: unpack_entry: left loop at a valid delta");
 		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
diff --git a/trailer.c b/trailer.c
index 8e48a5c..c6ea9ac 100644
--- a/trailer.c
+++ b/trailer.c
@@ -562,7 +562,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 		break;
 	default:
-		die("internal bug in trailer.c");
+		die("BUG: trailer.c: unhandled type %d", type);
 	}
 	return 0;
 }
diff --git a/transport.c b/transport.c
index 095e61f..52bf997 100644
--- a/transport.c
+++ b/transport.c
@@ -563,7 +563,7 @@ void transport_take_over(struct transport *transport,
 	struct git_transport_data *data;
 
 	if (!transport->smart_options)
-		die("Bug detected: Taking over transport requires non-NULL "
+		die("BUG: taking over transport requires non-NULL "
 		    "smart_options field.");
 
 	data = xcalloc(1, sizeof(*data));
diff --git a/wt-status.c b/wt-status.c
index 4ce4e35..311ae7c 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die(_("bug: unhandled unmerged status %x"), stagemask);
+		die(_("BUG: unhandled unmerged status %x"), stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die(_("bug: unhandled diff status %c"), status);
+		die(_("BUG: unhandled diff status %c"), status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
@ 2016-06-29 11:36 ` Johannes Schindelin
  2016-06-29 18:35   ` Junio C Hamano
  2016-06-29 11:36 ` [PATCH 3/9] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:36 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

It can be puzzling to see that was_tracked() tries to match an index
entry by name even if cache_name_pos() returned a negative value. Let's
clarify that cache_name_pos() implicitly looks for stage 0, while we are
also okay with finding other stages.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/merge-recursive.c b/merge-recursive.c
index 98f4632..bcb53f0 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -658,6 +658,7 @@ static int was_tracked(const char *path)
 {
 	int pos = cache_name_pos(path, strlen(path));
 
+	/* cache_name_pos() looks for stage == 0, so pos may be < 0 */
 	if (pos < 0)
 		pos = -1 - pos;
 	while (pos < active_nr &&
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 3/9] Prepare the builtins for a libified merge_recursive()
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
  2016-06-29 11:36 ` [PATCH 2/9] merge-recursive: clarify code in was_tracked() Johannes Schindelin
@ 2016-06-29 11:36 ` Johannes Schindelin
  2016-06-29 18:56   ` Junio C Hamano
  2016-06-29 11:36 ` [PATCH 4/9] merge_recursive: abort properly upon errors Johannes Schindelin
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:36 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

A truly libified function does not die() just for fun. As such, the
recursive merge will convert all die() calls to return -1 instead in the
next commits, giving the caller a chance at least to print some helpful
message.

Let's prepare the builtins for this fatal error condition, even if we do
not really do more than imitating the previous die()'s exit(128): this is
what callers of e.g. `git merge` have come to expect.

Note that the callers of the sequencer (revert and cherry-pick) already
fail fast even for the return value -1; The only difference is that they
now get a chance to say "<command> failed".

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 4 +++-
 builtin/merge.c    | 4 ++++
 sequencer.c        | 4 ++++
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index c3486bd..14312f7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -567,8 +567,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.ancestor = old->name;
 			o.branch1 = new->name;
 			o.branch2 = "local";
-			merge_trees(&o, new->commit->tree, work,
+			ret = merge_trees(&o, new->commit->tree, work,
 				old->commit->tree, &result);
+			if (ret < 0)
+				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
 			if (ret)
diff --git a/builtin/merge.c b/builtin/merge.c
index b555a1b..133b853 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -682,6 +682,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 		hold_locked_index(&lock, 1);
 		clean = merge_recursive(&o, head,
 				remoteheads->item, reversed, &result);
+		if (clean < 0)
+			exit(128);
 		if (active_cache_changed &&
 		    write_locked_index(&the_index, &lock, COMMIT_LOCK))
 			die (_("unable to write %s"), get_index_file());
@@ -1550,6 +1552,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		ret = try_merge_strategy(use_strategies[i]->name,
 					 common, remoteheads,
 					 head_commit, head_arg);
+		if (ret < 0)
+			exit(128);
 		if (!option_commit && !ret) {
 			merge_was_ok = 1;
 			/*
diff --git a/sequencer.c b/sequencer.c
index c6362d6..13b794a 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	if (clean < 0)
+		return clean;
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
@@ -561,6 +563,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
 					 head, &msgbuf, opts);
+		if (res < 0)
+			return res;
 		write_message(&msgbuf, git_path_merge_msg());
 	} else {
 		struct commit_list *common = NULL;
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 4/9] merge_recursive: abort properly upon errors
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (2 preceding siblings ...)
  2016-06-29 11:36 ` [PATCH 3/9] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
@ 2016-06-29 11:36 ` Johannes Schindelin
  2016-06-29 20:08   ` Junio C Hamano
  2016-06-29 11:36 ` [PATCH 5/9] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:36 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

There are a couple of places where return values indicating errors
are ignored. Let's teach them manners.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index bcb53f0..c4ece96 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1944,8 +1944,9 @@ int merge_recursive(struct merge_options *o,
 		saved_b2 = o->branch2;
 		o->branch1 = "Temporary merge branch 1";
 		o->branch2 = "Temporary merge branch 2";
-		merge_recursive(o, merged_common_ancestors, iter->item,
-				NULL, &merged_common_ancestors);
+		if (merge_recursive(o, merged_common_ancestors, iter->item,
+				NULL, &merged_common_ancestors) < 0)
+			return -1;
 		o->branch1 = saved_b1;
 		o->branch2 = saved_b2;
 		o->call_depth--;
@@ -1961,6 +1962,8 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	if (clean < 0)
+		return clean;
 
 	if (o->call_depth) {
 		*result = make_virtual_commit(mrtree, "merged tree");
@@ -2017,6 +2020,9 @@ int merge_recursive_generic(struct merge_options *o,
 	hold_locked_index(lock, 1);
 	clean = merge_recursive(o, head_commit, next_commit, ca,
 			result);
+	if (clean < 0)
+		return clean;
+
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
 		return error(_("Unable to write index."));
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 5/9] merge-recursive: avoid returning a wholesale struct
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (3 preceding siblings ...)
  2016-06-29 11:36 ` [PATCH 4/9] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-06-29 11:36 ` Johannes Schindelin
  2016-06-29 20:21   ` Junio C Hamano
  2016-06-29 11:37 ` [PATCH 6/9] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:36 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

It is technically allowed, as per C89, for functions' return type to
be complete structs (i.e. *not* just pointers to structs), but it is
bad practice.

This is a very late attempt to contain the damage done by this developer
in 6d297f8 (Status update on merge-recursive in C, 2006-07-08) which
introduced such a return type.

It will also help the current effort to libify merge-recursive.c, as
it will allow us to return proper error codes later.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 93 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 50 insertions(+), 43 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index c4ece96..d56651c9 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -888,47 +888,47 @@ static int merge_3way(struct merge_options *o,
 	return merge_status;
 }
 
-static struct merge_file_info merge_file_1(struct merge_options *o,
+static int merge_file_1(struct merge_options *o,
 					   const struct diff_filespec *one,
 					   const struct diff_filespec *a,
 					   const struct diff_filespec *b,
 					   const char *branch1,
-					   const char *branch2)
+					   const char *branch2,
+					   struct merge_file_info *result)
 {
-	struct merge_file_info result;
-	result.merge = 0;
-	result.clean = 1;
+	result->merge = 0;
+	result->clean = 1;
 
 	if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-		result.clean = 0;
+		result->clean = 0;
 		if (S_ISREG(a->mode)) {
-			result.mode = a->mode;
-			hashcpy(result.sha, a->sha1);
+			result->mode = a->mode;
+			hashcpy(result->sha, a->sha1);
 		} else {
-			result.mode = b->mode;
-			hashcpy(result.sha, b->sha1);
+			result->mode = b->mode;
+			hashcpy(result->sha, b->sha1);
 		}
 	} else {
 		if (!sha_eq(a->sha1, one->sha1) && !sha_eq(b->sha1, one->sha1))
-			result.merge = 1;
+			result->merge = 1;
 
 		/*
 		 * Merge modes
 		 */
 		if (a->mode == b->mode || a->mode == one->mode)
-			result.mode = b->mode;
+			result->mode = b->mode;
 		else {
-			result.mode = a->mode;
+			result->mode = a->mode;
 			if (b->mode != one->mode) {
-				result.clean = 0;
-				result.merge = 1;
+				result->clean = 0;
+				result->merge = 1;
 			}
 		}
 
 		if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, one->sha1))
-			hashcpy(result.sha, b->sha1);
+			hashcpy(result->sha, b->sha1);
 		else if (sha_eq(b->sha1, one->sha1))
-			hashcpy(result.sha, a->sha1);
+			hashcpy(result->sha, a->sha1);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
 			int merge_status;
@@ -940,62 +940,63 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 				die(_("Failed to execute internal merge"));
 
 			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result.sha))
+					    blob_type, result->sha))
 				die(_("Unable to add %s to database"),
 				    a->path);
 
 			free(result_buf.ptr);
-			result.clean = (merge_status == 0);
+			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
-			result.clean = merge_submodule(result.sha,
+			result->clean = merge_submodule(result->sha,
 						       one->path, one->sha1,
 						       a->sha1, b->sha1,
 						       !o->call_depth);
 		} else if (S_ISLNK(a->mode)) {
-			hashcpy(result.sha, a->sha1);
+			hashcpy(result->sha, a->sha1);
 
 			if (!sha_eq(a->sha1, b->sha1))
-				result.clean = 0;
+				result->clean = 0;
 		} else
 			die(_("BUG: unsupported object type in the tree"));
 	}
 
-	return result;
+	return 0;
 }
 
-static struct merge_file_info
-merge_file_special_markers(struct merge_options *o,
+static int merge_file_special_markers(struct merge_options *o,
 			   const struct diff_filespec *one,
 			   const struct diff_filespec *a,
 			   const struct diff_filespec *b,
 			   const char *branch1,
 			   const char *filename1,
 			   const char *branch2,
-			   const char *filename2)
+			   const char *filename2,
+			   struct merge_file_info *mfi)
 {
 	char *side1 = NULL;
 	char *side2 = NULL;
-	struct merge_file_info mfi;
+	int ret;
 
 	if (filename1)
 		side1 = xstrfmt("%s:%s", branch1, filename1);
 	if (filename2)
 		side2 = xstrfmt("%s:%s", branch2, filename2);
 
-	mfi = merge_file_1(o, one, a, b,
-			   side1 ? side1 : branch1, side2 ? side2 : branch2);
+	ret = merge_file_1(o, one, a, b,
+		side1 ? side1 : branch1, side2 ? side2 : branch2, mfi);
 	free(side1);
 	free(side2);
-	return mfi;
+	return ret;
 }
 
-static struct merge_file_info merge_file_one(struct merge_options *o,
+static int merge_file_one(struct merge_options *o,
 					 const char *path,
 					 const unsigned char *o_sha, int o_mode,
 					 const unsigned char *a_sha, int a_mode,
 					 const unsigned char *b_sha, int b_mode,
 					 const char *branch1,
-					 const char *branch2)
+					 const char *branch2,
+					 struct merge_file_info *mfi)
 {
 	struct diff_filespec one, a, b;
 
@@ -1006,7 +1007,7 @@ static struct merge_file_info merge_file_one(struct merge_options *o,
 	a.mode = a_mode;
 	hashcpy(b.sha1, b_sha);
 	b.mode = b_mode;
-	return merge_file_1(o, &one, &a, &b, branch1, branch2);
+	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
 static void handle_change_delete(struct merge_options *o,
@@ -1179,11 +1180,14 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		struct merge_file_info mfi;
 		struct diff_filespec other;
 		struct diff_filespec *add;
-		mfi = merge_file_one(o, one->path,
+
+		if (merge_file_one(o, one->path,
 				 one->sha1, one->mode,
 				 a->sha1, a->mode,
 				 b->sha1, b->mode,
-				 ci->branch1, ci->branch2);
+				 ci->branch1, ci->branch2, &mfi) < 0)
+			return;
+
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
@@ -1237,12 +1241,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
-	mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
 					    o->branch1, c1->path,
-					    o->branch2, ci->ren1_other.path);
-	mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+					    o->branch2, ci->ren1_other.path, &mfi_c1) < 0 ||
+	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 					    o->branch1, ci->ren2_other.path,
-					    o->branch2, c2->path);
+					    o->branch2, c2->path, &mfi_c2) < 0)
+		return;
 
 	if (o->call_depth) {
 		/*
@@ -1463,10 +1468,11 @@ static int process_renames(struct merge_options *o,
 				       ren1_dst, branch2);
 				if (o->call_depth) {
 					struct merge_file_info mfi;
-					mfi = merge_file_one(o, ren1_dst, null_sha1, 0,
+					if (merge_file_one(o, ren1_dst, null_sha1, 0,
 							 ren1->pair->two->sha1, ren1->pair->two->mode,
 							 dst_other.sha1, dst_other.mode,
-							 branch1, branch2);
+							 branch1, branch2, &mfi) < 0)
+						return -1;
 					output(o, 1, _("Adding merged %s"), ren1_dst);
 					update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
 					try_merge = 0;
@@ -1622,9 +1628,10 @@ static int merge_content(struct merge_options *o,
 		if (dir_in_way(path, !o->call_depth))
 			df_conflict_remains = 1;
 	}
-	mfi = merge_file_special_markers(o, &one, &a, &b,
+	if (merge_file_special_markers(o, &one, &a, &b,
 					 o->branch1, path1,
-					 o->branch2, path2);
+					 o->branch2, path2, &mfi) < 0)
+		return -1;
 
 	if (mfi.clean && !df_conflict_remains &&
 	    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 6/9] merge-recursive: allow write_tree_from_memory() to error out
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (4 preceding siblings ...)
  2016-06-29 11:36 ` [PATCH 5/9] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
@ 2016-06-29 11:37 ` Johannes Schindelin
  2016-06-29 11:37 ` [PATCH 7/9] merge-recursive: handle return values indicating errors Johannes Schindelin
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

It is possible that a tree cannot be written (think: disk full). We
will want to give the caller a chance to clean up instead of letting
the program die() in such a case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index d56651c9..6ab7dfc 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1875,8 +1875,8 @@ int merge_trees(struct merge_options *o,
 	else
 		clean = 1;
 
-	if (o->call_depth)
-		*result = write_tree_from_memory(o);
+	if (o->call_depth && !(*result = write_tree_from_memory(o)))
+		return -1;
 
 	return clean;
 }
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 7/9] merge-recursive: handle return values indicating errors
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (5 preceding siblings ...)
  2016-06-29 11:37 ` [PATCH 6/9] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
@ 2016-06-29 11:37 ` Johannes Schindelin
  2016-06-29 21:06   ` Junio C Hamano
  2016-06-29 11:37 ` [PATCH 8/9] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

We are about to libify the recursive merge machinery, where we only
die() in case of a bug or memory contention. To that end, we must heed
negative return values as indicating errors.

This requires our functions to be careful to pass through error
conditions in call chains, and for quite a few functions this means
that they have to return values to begin with.

The next step will be to convert the places where we currently die() to
return negative values (read: -1) instead.

Note that we ignore errors reported by make_room_for_path(), consistent
with the previous behavior (update_file_flags() used the return value of
make_room_for_path() only to indicate an early return, but not a fatal
error): if the error is really a fatal error, we will notice later; If
not, it was not that serious a problem to begin with. (Witnesses in
favor of this reasoning are t4151-am-abort and t7610-mergetool, which
would start failing if we stopped on errors reported by
make_room_for_path()).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 251 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 158 insertions(+), 93 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 6ab7dfc..bb075e3 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -266,8 +266,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 		active_cache_tree = cache_tree();
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
-	    cache_tree_update(&the_index, 0) < 0)
-		die(_("error building trees"));
+	    cache_tree_update(&the_index, 0) < 0) {
+		error(_("error building trees"));
+		return NULL;
+	}
 
 	result = lookup_tree(active_cache_tree->sha1);
 
@@ -548,19 +550,17 @@ static int update_stages(const char *path, const struct diff_filespec *o,
 	 */
 	int clear = 1;
 	int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
+	int ret = 0;
+
 	if (clear)
-		if (remove_file_from_cache(path))
-			return -1;
-	if (o)
-		if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
-			return -1;
-	if (a)
-		if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
-			return -1;
-	if (b)
-		if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
-			return -1;
-	return 0;
+		ret = remove_file_from_cache(path);
+	if (!ret && o)
+		ret = add_cacheinfo(o->mode, o->sha1, path, 1, 0, options);
+	if (!ret && a)
+		ret = add_cacheinfo(a->mode, a->sha1, path, 2, 0, options);
+	if (!ret && b)
+		ret = add_cacheinfo(b->mode, b->sha1, path, 3, 0, options);
+	return ret;
 }
 
 static void update_entry(struct stage_data *entry,
@@ -736,7 +736,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	return error(msg, path, _(": perhaps a D/F conflict?"));
 }
 
-static void update_file_flags(struct merge_options *o,
+static int update_file_flags(struct merge_options *o,
 			      const unsigned char *sha,
 			      unsigned mode,
 			      const char *path,
@@ -777,8 +777,7 @@ static void update_file_flags(struct merge_options *o,
 
 		if (make_room_for_path(o, path) < 0) {
 			update_wd = 0;
-			free(buf);
-			goto update_index;
+			goto free_buf;
 		}
 		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
 			int fd;
@@ -801,20 +800,22 @@ static void update_file_flags(struct merge_options *o,
 		} else
 			die(_("do not know what to do with %06o %s '%s'"),
 			    mode, sha1_to_hex(sha), path);
+free_buf:
 		free(buf);
 	}
  update_index:
 	if (update_cache)
 		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+	return 0;
 }
 
-static void update_file(struct merge_options *o,
+static int update_file(struct merge_options *o,
 			int clean,
 			const unsigned char *sha,
 			unsigned mode,
 			const char *path)
 {
-	update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth);
+	return update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth);
 }
 
 /* Low level file merging, update and removal */
@@ -1010,7 +1011,7 @@ static int merge_file_one(struct merge_options *o,
 	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
-static void handle_change_delete(struct merge_options *o,
+static int handle_change_delete(struct merge_options *o,
 				 const char *path,
 				 const unsigned char *o_sha, int o_mode,
 				 const unsigned char *a_sha, int a_mode,
@@ -1018,6 +1019,7 @@ static void handle_change_delete(struct merge_options *o,
 				 const char *change, const char *change_past)
 {
 	char *renamed = NULL;
+	int ret = 0;
 	if (dir_in_way(path, !o->call_depth)) {
 		renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
 	}
@@ -1028,21 +1030,23 @@ static void handle_change_delete(struct merge_options *o,
 		 * correct; since there is no true "middle point" between
 		 * them, simply reuse the base version for virtual merge base.
 		 */
-		remove_file_from_cache(path);
-		update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
+		ret = remove_file_from_cache(path);
+		if (!ret)
+			ret = update_file(o, 0, o_sha, o_mode,
+					  renamed ? renamed : path);
 	} else if (!a_sha) {
 		if (!renamed) {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path);
-			update_file(o, 0, b_sha, b_mode, path);
+			ret = update_file(o, 0, b_sha, b_mode, path);
 		} else {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path, renamed);
-			update_file(o, 0, b_sha, b_mode, renamed);
+			ret = update_file(o, 0, b_sha, b_mode, renamed);
 		}
 	} else {
 		if (!renamed) {
@@ -1055,7 +1059,7 @@ static void handle_change_delete(struct merge_options *o,
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch2, change_past,
 			       o->branch1, o->branch1, path, renamed);
-			update_file(o, 0, a_sha, a_mode, renamed);
+			ret = update_file(o, 0, a_sha, a_mode, renamed);
 		}
 		/*
 		 * No need to call update_file() on path when !renamed, since
@@ -1065,9 +1069,11 @@ static void handle_change_delete(struct merge_options *o,
 		 */
 	}
 	free(renamed);
+
+	return ret;
 }
 
-static void conflict_rename_delete(struct merge_options *o,
+static int conflict_rename_delete(struct merge_options *o,
 				   struct diff_filepair *pair,
 				   const char *rename_branch,
 				   const char *other_branch)
@@ -1078,6 +1084,7 @@ static void conflict_rename_delete(struct merge_options *o,
 	const unsigned char *b_sha = NULL;
 	int a_mode = 0;
 	int b_mode = 0;
+	int ret = 0;
 
 	if (rename_branch == o->branch1) {
 		a_sha = dest->sha1;
@@ -1087,21 +1094,22 @@ static void conflict_rename_delete(struct merge_options *o,
 		b_mode = dest->mode;
 	}
 
-	handle_change_delete(o,
+	ret = handle_change_delete(o,
 			     o->call_depth ? orig->path : dest->path,
 			     orig->sha1, orig->mode,
 			     a_sha, a_mode,
 			     b_sha, b_mode,
 			     _("rename"), _("renamed"));
-
-	if (o->call_depth) {
-		remove_file_from_cache(dest->path);
-	} else {
-		update_stages(dest->path, NULL,
+	if (ret < 0)
+		return ret;
+	if (o->call_depth)
+		ret = remove_file_from_cache(dest->path);
+	else
+		ret = update_stages(dest->path, NULL,
 			      rename_branch == o->branch1 ? dest : NULL,
 			      rename_branch == o->branch1 ? NULL : dest);
-	}
 
+	return ret;
 }
 
 static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
@@ -1117,7 +1125,7 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 	return target;
 }
 
-static void handle_file(struct merge_options *o,
+static int handle_file(struct merge_options *o,
 			struct diff_filespec *rename,
 			int stage,
 			struct rename_conflict_info *ci)
@@ -1127,6 +1135,7 @@ static void handle_file(struct merge_options *o,
 	const char *cur_branch, *other_branch;
 	struct diff_filespec other;
 	struct diff_filespec *add;
+	int ret;
 
 	if (stage == 2) {
 		dst_entry = ci->dst_entry1;
@@ -1141,7 +1150,8 @@ static void handle_file(struct merge_options *o,
 	add = filespec_from_entry(&other, dst_entry, stage ^ 1);
 	if (add) {
 		char *add_name = unique_path(o, rename->path, other_branch);
-		update_file(o, 0, add->sha1, add->mode, add_name);
+		if ((ret = update_file(o, 0, add->sha1, add->mode, add_name)))
+			return ret;
 
 		remove_file(o, 0, rename->path, 0);
 		dst_name = unique_path(o, rename->path, cur_branch);
@@ -1152,23 +1162,27 @@ static void handle_file(struct merge_options *o,
 			       rename->path, other_branch, dst_name);
 		}
 	}
-	update_file(o, 0, rename->sha1, rename->mode, dst_name);
-	if (stage == 2)
-		update_stages(rename->path, NULL, rename, add);
+	if ((ret = update_file(o, 0, rename->sha1, rename->mode, dst_name)))
+		; /* fall through, do allow dst_name to be released */
+	else if (stage == 2)
+		ret = update_stages(rename->path, NULL, rename, add);
 	else
-		update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
+
+	return ret;
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
+static int conflict_rename_rename_1to2(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* One file was renamed in both branches, but to different names. */
 	struct diff_filespec *one = ci->pair1->one;
 	struct diff_filespec *a = ci->pair1->two;
 	struct diff_filespec *b = ci->pair2->two;
+	int ret = 0;
 
 	output(o, 1, _("CONFLICT (rename/rename): "
 	       "Rename \"%s\"->\"%s\" in branch \"%s\" "
@@ -1181,12 +1195,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		struct diff_filespec other;
 		struct diff_filespec *add;
 
-		if (merge_file_one(o, one->path,
+		if ((ret = merge_file_one(o, one->path,
 				 one->sha1, one->mode,
 				 a->sha1, a->mode,
 				 b->sha1, b->mode,
-				 ci->branch1, ci->branch2, &mfi) < 0)
-			return;
+				 ci->branch1, ci->branch2, &mfi)))
+			return ret;
 
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
@@ -1194,7 +1208,8 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * pathname and then either rename the add-source file to that
 		 * unique path, or use that unique path instead of src here.
 		 */
-		update_file(o, 0, mfi.sha, mfi.mode, one->path);
+		if ((ret = update_file(o, 0, mfi.sha, mfi.mode, one->path)))
+			return ret;
 
 		/*
 		 * Above, we put the merged content at the merge-base's
@@ -1205,22 +1220,31 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * resolving the conflict at that path in its favor.
 		 */
 		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
-		if (add)
-			update_file(o, 0, add->sha1, add->mode, a->path);
+		if (add) {
+			if ((ret = update_file(o, 0, add->sha1, add->mode,
+					a->path)))
+				return ret;
+		}
 		else
 			remove_file_from_cache(a->path);
 		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
-		if (add)
-			update_file(o, 0, add->sha1, add->mode, b->path);
+		if (add) {
+			if ((ret = update_file(o, 0, add->sha1, add->mode,
+					b->path)))
+				return ret;
+		}
 		else
 			remove_file_from_cache(b->path);
 	} else {
-		handle_file(o, a, 2, ci);
-		handle_file(o, b, 3, ci);
+		if ((ret = handle_file(o, a, 2, ci)) ||
+		    (ret = handle_file(o, b, 3, ci)))
+			return ret;
 	}
+
+	return ret;
 }
 
-static void conflict_rename_rename_2to1(struct merge_options *o,
+static int conflict_rename_rename_2to1(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* Two files, a & b, were renamed to the same thing, c. */
@@ -1231,6 +1255,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	char *path = c1->path; /* == c2->path */
 	struct merge_file_info mfi_c1;
 	struct merge_file_info mfi_c2;
+	int ret;
 
 	output(o, 1, _("CONFLICT (rename/rename): "
 	       "Rename %s->%s in %s. "
@@ -1241,13 +1266,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
-	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
+	if ((ret = merge_file_special_markers(o, a, c1, &ci->ren1_other,
 					    o->branch1, c1->path,
-					    o->branch2, ci->ren1_other.path, &mfi_c1) < 0 ||
-	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
+					    o->branch2, ci->ren1_other.path, &mfi_c1)) ||
+	    (ret = merge_file_special_markers(o, b, &ci->ren2_other, c2,
 					    o->branch1, ci->ren2_other.path,
-					    o->branch2, c2->path, &mfi_c2) < 0)
-		return;
+					    o->branch2, c2->path, &mfi_c2)))
+		return ret;
 
 	if (o->call_depth) {
 		/*
@@ -1258,26 +1283,32 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 		 * again later for the non-recursive merge.
 		 */
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
-		update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);
+		ret = update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
+		if (!ret)
+			ret = update_file(o, 0, mfi_c2.sha, mfi_c2.mode,
+				b->path);
 	} else {
 		char *new_path1 = unique_path(o, path, ci->branch1);
 		char *new_path2 = unique_path(o, path, ci->branch2);
 		output(o, 1, _("Renaming %s to %s and %s to %s instead"),
 		       a->path, new_path1, b->path, new_path2);
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
-		update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
+		ret = update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
+		if (!ret)
+			ret = update_file(o, 0, mfi_c2.sha, mfi_c2.mode,
+				new_path2);
 		free(new_path2);
 		free(new_path1);
 	}
+
+	return ret;
 }
 
 static int process_renames(struct merge_options *o,
 			   struct string_list *a_renames,
 			   struct string_list *b_renames)
 {
-	int clean_merge = 1, i, j;
+	int clean_merge = 1, i, j, ret;
 	struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
 	struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
 	const struct rename *sre;
@@ -1453,12 +1484,14 @@ static int process_renames(struct merge_options *o,
 				 * update_file_flags() instead of
 				 * update_file().
 				 */
-				update_file_flags(o,
+				ret = update_file_flags(o,
 						  ren1->pair->two->sha1,
 						  ren1->pair->two->mode,
 						  ren1_dst,
 						  1, /* update_cache */
 						  0  /* update_wd    */);
+				if (ret)
+					clean_merge = ret;
 			} else if (!sha_eq(dst_other.sha1, null_sha1)) {
 				clean_merge = 0;
 				try_merge = 1;
@@ -1468,23 +1501,29 @@ static int process_renames(struct merge_options *o,
 				       ren1_dst, branch2);
 				if (o->call_depth) {
 					struct merge_file_info mfi;
-					if (merge_file_one(o, ren1_dst, null_sha1, 0,
+					if ((ret = merge_file_one(o, ren1_dst, null_sha1, 0,
 							 ren1->pair->two->sha1, ren1->pair->two->mode,
 							 dst_other.sha1, dst_other.mode,
-							 branch1, branch2, &mfi) < 0)
-						return -1;
+							 branch1, branch2, &mfi))) {
+						clean_merge = ret;
+						goto cleanup_and_return;
+					}
 					output(o, 1, _("Adding merged %s"), ren1_dst);
-					update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
+					if ((ret = update_file(o, 0, mfi.sha, mfi.mode, ren1_dst)))
+						clean_merge = ret;
 					try_merge = 0;
 				} else {
 					char *new_path = unique_path(o, ren1_dst, branch2);
 					output(o, 1, _("Adding as %s instead"), new_path);
-					update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+					if ((ret = update_file(o, 0, dst_other.sha1, dst_other.mode, new_path)))
+						clean_merge = ret;
 					free(new_path);
 				}
 			} else
 				try_merge = 1;
 
+			if (clean_merge < 0)
+				goto cleanup_and_return;
 			if (try_merge) {
 				struct diff_filespec *one, *a, *b;
 				src_other.path = (char *)ren1_src;
@@ -1511,6 +1550,7 @@ static int process_renames(struct merge_options *o,
 			}
 		}
 	}
+cleanup_and_return:
 	string_list_clear(&a_by_dst, 0);
 	string_list_clear(&b_by_dst, 0);
 
@@ -1573,13 +1613,13 @@ error_return:
 	return ret;
 }
 
-static void handle_modify_delete(struct merge_options *o,
+static int handle_modify_delete(struct merge_options *o,
 				 const char *path,
 				 unsigned char *o_sha, int o_mode,
 				 unsigned char *a_sha, int a_mode,
 				 unsigned char *b_sha, int b_mode)
 {
-	handle_change_delete(o,
+	return handle_change_delete(o,
 			     path,
 			     o_sha, o_mode,
 			     a_sha, a_mode,
@@ -1599,6 +1639,7 @@ static int merge_content(struct merge_options *o,
 	struct merge_file_info mfi;
 	struct diff_filespec one, a, b;
 	unsigned df_conflict_remains = 0;
+	int ret;
 
 	if (!o_sha) {
 		reason = _("add/add");
@@ -1628,10 +1669,10 @@ static int merge_content(struct merge_options *o,
 		if (dir_in_way(path, !o->call_depth))
 			df_conflict_remains = 1;
 	}
-	if (merge_file_special_markers(o, &one, &a, &b,
+	if ((ret = merge_file_special_markers(o, &one, &a, &b,
 					 o->branch1, path1,
-					 o->branch2, path2, &mfi) < 0)
-		return -1;
+					 o->branch2, path2, &mfi)))
+		return ret;
 
 	if (mfi.clean && !df_conflict_remains &&
 	    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
@@ -1658,7 +1699,8 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			update_stages(path, &one, &a, &b);
+			if ((ret = update_stages(path, &one, &a, &b)))
+				return ret;
 	}
 
 	if (df_conflict_remains) {
@@ -1666,27 +1708,33 @@ static int merge_content(struct merge_options *o,
 		if (o->call_depth) {
 			remove_file_from_cache(path);
 		} else {
-			if (!mfi.clean)
-				update_stages(path, &one, &a, &b);
-			else {
+			if (!mfi.clean) {
+				if ((ret = update_stages(path, &one, &a, &b)))
+					return ret;
+			} else {
 				int file_from_stage2 = was_tracked(path);
 				struct diff_filespec merged;
 				hashcpy(merged.sha1, mfi.sha);
 				merged.mode = mfi.mode;
 
-				update_stages(path, NULL,
+				if ((ret = update_stages(path, NULL,
 					      file_from_stage2 ? &merged : NULL,
-					      file_from_stage2 ? NULL : &merged);
+					      file_from_stage2 ? NULL : &merged)))
+					return ret;
 			}
 
 		}
 		new_path = unique_path(o, path, rename_conflict_info->branch1);
 		output(o, 1, _("Adding as %s instead"), new_path);
-		update_file(o, 0, mfi.sha, mfi.mode, new_path);
+		if ((ret = update_file(o, 0, mfi.sha, mfi.mode, new_path))) {
+			free(new_path);
+			return ret;
+		}
 		free(new_path);
 		mfi.clean = 0;
 	} else {
-		update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+		if ((ret = update_file(o, mfi.clean, mfi.sha, mfi.mode, path)))
+			return ret;
 	}
 	return mfi.clean;
 
@@ -1696,7 +1744,7 @@ static int merge_content(struct merge_options *o,
 static int process_entry(struct merge_options *o,
 			 const char *path, struct stage_data *entry)
 {
-	int clean_merge = 1;
+	int clean_merge = 1, ret;
 	int normalize = o->renormalize;
 	unsigned o_mode = entry->stages[1].mode;
 	unsigned a_mode = entry->stages[2].mode;
@@ -1717,17 +1765,23 @@ static int process_entry(struct merge_options *o,
 			break;
 		case RENAME_DELETE:
 			clean_merge = 0;
-			conflict_rename_delete(o, conflict_info->pair1,
+			if ((ret = conflict_rename_delete(o,
+					       conflict_info->pair1,
 					       conflict_info->branch1,
-					       conflict_info->branch2);
+					       conflict_info->branch2)))
+				clean_merge = ret;
 			break;
 		case RENAME_ONE_FILE_TO_TWO:
 			clean_merge = 0;
-			conflict_rename_rename_1to2(o, conflict_info);
+			if ((ret = conflict_rename_rename_1to2(o,
+					conflict_info)))
+				clean_merge = ret;
 			break;
 		case RENAME_TWO_FILES_TO_ONE:
 			clean_merge = 0;
-			conflict_rename_rename_2to1(o, conflict_info);
+			if ((ret = conflict_rename_rename_2to1(o,
+					conflict_info)))
+				clean_merge = ret;
 			break;
 		default:
 			entry->processed = 0;
@@ -1747,8 +1801,9 @@ static int process_entry(struct merge_options *o,
 		} else {
 			/* Modify/delete; deleted side may have put a directory in the way */
 			clean_merge = 0;
-			handle_modify_delete(o, path, o_sha, o_mode,
-					     a_sha, a_mode, b_sha, b_mode);
+			if ((ret = handle_modify_delete(o, path, o_sha, o_mode,
+					a_sha, a_mode, b_sha, b_mode)))
+				clean_merge = ret;
 		}
 	} else if ((!o_sha && a_sha && !b_sha) ||
 		   (!o_sha && !a_sha && b_sha)) {
@@ -1780,14 +1835,18 @@ static int process_entry(struct merge_options *o,
 			output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
 			       "Adding %s as %s"),
 			       conf, path, other_branch, path, new_path);
-			update_file(o, 0, sha, mode, new_path);
-			if (o->call_depth)
+			ret = update_file(o, 0, sha, mode, new_path);
+			if (ret)
+				clean_merge = ret;
+			else if (o->call_depth)
 				remove_file_from_cache(path);
 			free(new_path);
 		} else {
 			output(o, 2, _("Adding %s"), path);
 			/* do not overwrite file if already present */
-			update_file_flags(o, sha, mode, path, 1, !a_sha);
+			if ((ret = update_file_flags(o, sha, mode, path, 1,
+					!a_sha)))
+				clean_merge = ret;
 		}
 	} else if (a_sha && b_sha) {
 		/* Case C: Added in both (check for same permissions) and */
@@ -1850,12 +1909,18 @@ int merge_trees(struct merge_options *o,
 		re_head  = get_renames(o, head, common, head, merge, entries);
 		re_merge = get_renames(o, merge, common, head, merge, entries);
 		clean = process_renames(o, re_head, re_merge);
+		if (clean < 0)
+			return clean;
 		for (i = entries->nr-1; 0 <= i; i--) {
 			const char *path = entries->items[i].string;
 			struct stage_data *e = entries->items[i].util;
-			if (!e->processed
-				&& !process_entry(o, path, e))
-				clean = 0;
+			if (!e->processed) {
+				int ret = process_entry(o, path, e);
+				if (!ret)
+					clean = 0;
+				else if (ret < 0)
+					return ret;
+			}
 		}
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 8/9] merge-recursive: switch to returning errors instead of dying
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (6 preceding siblings ...)
  2016-06-29 11:37 ` [PATCH 7/9] merge-recursive: handle return values indicating errors Johannes Schindelin
@ 2016-06-29 11:37 ` Johannes Schindelin
  2016-06-29 21:19   ` Junio C Hamano
  2016-06-29 11:38 ` [PATCH 9/9] am: make a direct call to merge_recursive Johannes Schindelin
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
  9 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

The recursive merge machinery is supposed to be a library function, i.e.
it should return an error when it fails. Originally the functions were
part of the builtin "merge-recursive", though, where it was simpler to
call die() and be done with error handling.

The existing callers were already prepared to detect negative return
values to indicate errors and to behave as previously: exit with code 128
(which is the same thing that die() does, after printing the message).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 55 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 25 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index bb075e3..d5a593c 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -710,12 +710,10 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	/* Make sure leading directories are created */
 	status = safe_create_leading_directories_const(path);
 	if (status) {
-		if (status == SCLD_EXISTS) {
+		if (status == SCLD_EXISTS)
 			/* something else exists */
-			error(msg, path, _(": perhaps a D/F conflict?"));
-			return -1;
-		}
-		die(msg, path, "");
+			return error(msg, path, _(": perhaps a D/F conflict?"));
+		return error(msg, path, "");
 	}
 
 	/*
@@ -743,6 +741,8 @@ static int update_file_flags(struct merge_options *o,
 			      int update_cache,
 			      int update_wd)
 {
+	int ret = 0;
+
 	if (o->call_depth)
 		update_wd = 0;
 
@@ -763,9 +763,11 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(sha, &type, &size);
 		if (!buf)
-			die(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
-		if (type != OBJ_BLOB)
-			die(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
+			return error(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
+		if (type != OBJ_BLOB) {
+			ret = error(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
+			goto free_buf;
+		}
 		if (S_ISREG(mode)) {
 			struct strbuf strbuf = STRBUF_INIT;
 			if (convert_to_working_tree(path, buf, size, &strbuf)) {
@@ -786,8 +788,10 @@ static int update_file_flags(struct merge_options *o,
 			else
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-			if (fd < 0)
-				die_errno(_("failed to open '%s'"), path);
+			if (fd < 0) {
+				ret = error_errno(_("failed to open '%s'"), path);
+				goto free_buf;
+			}
 			write_in_full(fd, buf, size);
 			close(fd);
 		} else if (S_ISLNK(mode)) {
@@ -795,18 +799,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				die_errno(_("failed to symlink '%s'"), path);
+				ret = error_errno(_("failed to symlink '%s'"), path);
 			free(lnk);
 		} else
-			die(_("do not know what to do with %06o %s '%s'"),
-			    mode, sha1_to_hex(sha), path);
+			ret = error_errno(_("do not know what to do with %06o %s '%s'"),
+				mode, sha1_to_hex(sha), path);
 free_buf:
 		free(buf);
 	}
  update_index:
-	if (update_cache)
+	if (!ret && update_cache)
 		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-	return 0;
+	return ret;
 }
 
 static int update_file(struct merge_options *o,
@@ -932,20 +936,22 @@ static int merge_file_1(struct merge_options *o,
 			hashcpy(result->sha, a->sha1);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
-			int merge_status;
+			int ret = 0, merge_status;
 
 			merge_status = merge_3way(o, &result_buf, one, a, b,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				die(_("Failed to execute internal merge"));
+				ret = error(_("Failed to execute internal merge"));
 
-			if (write_sha1_file(result_buf.ptr, result_buf.size,
+			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 					    blob_type, result->sha))
-				die(_("Unable to add %s to database"),
-				    a->path);
+				ret = error(_("Unable to add %s to database"),
+					a->path);
 
 			free(result_buf.ptr);
+			if (ret)
+				return ret;
 			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
 			result->clean = merge_submodule(result->sha,
@@ -1861,7 +1867,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("Fatal merge failure, shouldn't happen."));
+		return error(_("Fatal merge failure, shouldn't happen."));
 
 	return clean_merge;
 }
@@ -1889,11 +1895,10 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			die(_("merging of trees %s and %s failed"),
+			error(_("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
-		else
-			exit(128);
+		return -1;
 	}
 
 	if (unmerged_cache()) {
@@ -2024,7 +2029,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			die(_("merge returned no commit"));
+			return error(_("merge returned no commit"));
 	}
 
 	discard_cache();
-- 
2.9.0.268.gcabc8b0



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH 9/9] am: make a direct call to merge_recursive
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (7 preceding siblings ...)
  2016-06-29 11:37 ` [PATCH 8/9] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
@ 2016-06-29 11:38 ` Johannes Schindelin
  2016-06-29 17:45   ` Junio C Hamano
  2016-06-29 21:23   ` Junio C Hamano
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
  9 siblings, 2 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 11:38 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Junio C Hamano

From: Junio C Hamano <gitster@pobox.com>

Instead of spawning merge-recursive via run_command() in
run_fallback_merge_recursive(), make a direct call to the internal
merge_recursive_generic().

Here is a quick benchmark result, applying a patch for b4391657
(merge: drop 'git merge <message> HEAD <commit>' syntax, 2015-03-25)
that was still cooking in 'next' on 4b1fd356 (git-multimail: update
to release 1.2.0, 2015-10-11) which was the tip of 'master' at some
stage, on an x86-64 running Ubuntu:

      real    0m0.169s                      real    0m0.163s
      user    0m0.108s                      user    0m0.134s
      sys     0m0.068s                      sys     0m0.033s

      real    0m0.175s                      real    0m0.161s
      user    0m0.110s                      user    0m0.120s
      sys     0m0.066s                      sys     0m0.047s

      real    0m0.168s                      real    0m0.162s
      user    0m0.124s                      user    0m0.114s
      sys     0m0.045s                      sys     0m0.051s

      real    0m0.167s                      real    0m0.152s
      user    0m0.124s                      user    0m0.122s
      sys     0m0.045s                      sys     0m0.031s

      real    0m0.169s                      real    0m0.164s
      user    0m0.131s                      user    0m0.129s
      sys     0m0.043s                      sys     0m0.041s

Left-hand side shows the original, right-hand side shows the result
of this optimization.

Timings on Windows:

original:
0.00user 0.01system 0:00.29elapsed
0.00user 0.00system 0:00.25elapsed
0.01user 0.00system 0:00.24elapsed
0.01user 0.00system 0:00.26elapsed
0.00user 0.01system 0:00.23elapsed

with optimization:
0.00user 0.01system 0:00.22elapsed
0.00user 0.00system 0:00.25elapsed
0.00user 0.01system 0:00.22elapsed
0.00user 0.00system 0:00.22elapsed
0.01user 0.00system 0:00.21elapsed

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---

	It feels *slightly* wrong to submit your own patch to review,
	however, please keep in mind that

	1) I changed the patch (o.gently does not exist anymore, so I do
	   not set it), and

	2) I added my own timings performed on Windows.

 builtin/am.c | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 3dfe70b..dd41154 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1587,25 +1587,26 @@ static int run_fallback_merge_recursive(const struct am_state *state,
 					unsigned char *our_tree,
 					unsigned char *his_tree)
 {
-	struct child_process cp = CHILD_PROCESS_INIT;
+	const unsigned char *bases[1] = {orig_tree};
+	struct merge_options o;
+	struct commit *result;
+	char *his_tree_name;
 	int status;
 
-	cp.git_cmd = 1;
+	init_merge_options(&o);
+
+	o.branch1 = "HEAD";
+	his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+	o.branch2 = his_tree_name;
 
-	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
-			 sha1_to_hex(his_tree), linelen(state->msg), state->msg);
 	if (state->quiet)
-		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
+		o.verbosity = 0;
 
-	argv_array_push(&cp.args, "merge-recursive");
-	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
-	argv_array_push(&cp.args, "--");
-	argv_array_push(&cp.args, sha1_to_hex(our_tree));
-	argv_array_push(&cp.args, sha1_to_hex(his_tree));
+	status = merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result);
+	if (status < 0)
+		exit(128);
+	free(his_tree_name);
 
-	status = run_command(&cp) ? (-1) : 0;
-	discard_cache();
-	read_cache();
 	return status;
 }
 
-- 
2.9.0.268.gcabc8b0

^ permalink raw reply related	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
@ 2016-06-29 15:11   ` Johannes Schindelin
  2016-06-29 18:12   ` Eric Sunshine
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-29 15:11 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Hi,

On Wed, 29 Jun 2016, Johannes Schindelin wrote:

> diff --git a/imap-send.c b/imap-send.c
> index 938c691..cd39805 100644
> --- a/imap-send.c
> +++ b/imap-send.c
> @@ -511,7 +511,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
>  
>  	va_start(va, fmt);
>  	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
> -		die("Fatal: buffer too small. Please report a bug.");
> +		die("BUG: buffer too small (%d < %d)", ret, blen);
>  	va_end(va);
>  	return ret;
>  }

Oy vey. Travis CI found a bug here, thanks to clang's quite smart
checking. This here fixup is needed (will make that part of v2, but wait
for comments):

-- snipsnap --
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Subject: [PATCH] fixup! Report bugs consistently

---
 imap-send.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/imap-send.c b/imap-send.c
index cd39805..369f72a 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -506,7 +506,7 @@ static char *next_arg(char **s)
 
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
-	int ret;
+	int ret = -1;
 	va_list va;
 
 	va_start(va, fmt);
-- 
2.9.0.270.g810e421



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* Re: [PATCH 9/9] am: make a direct call to merge_recursive
  2016-06-29 11:38 ` [PATCH 9/9] am: make a direct call to merge_recursive Johannes Schindelin
@ 2016-06-29 17:45   ` Junio C Hamano
  2016-06-30  8:38     ` Johannes Schindelin
  2016-06-29 21:23   ` Junio C Hamano
  1 sibling, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 17:45 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> From: Junio C Hamano <gitster@pobox.com>

Did I write this thing?

Having two sets of numbers that illustrated that this is not really
a useful optimization in the bigger picture looks vaguely familiar
(e.g. $gmane/279417), but the numbers are different.

> 	It feels *slightly* wrong to submit your own patch to review,
> 	however, please keep in mind that
>
> 	1) I changed the patch (o.gently does not exist anymore, so I do
> 	   not set it), and
>
> 	2) I added my own timings performed on Windows.

It probably is much less confusing if you take the authorship,
possibly with a passing reference to whatever I wrote as the source
of inspiration in the log message, or something.  I do not think I
deserve a credit in this 9-patch series.

I haven't read the other 8 patches, so I cannot comment on it yet.

>  builtin/am.c | 27 ++++++++++++++-------------
>  1 file changed, 14 insertions(+), 13 deletions(-)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index 3dfe70b..dd41154 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -1587,25 +1587,26 @@ static int run_fallback_merge_recursive(const struct am_state *state,
>  					unsigned char *our_tree,
>  					unsigned char *his_tree)
>  {
> -	struct child_process cp = CHILD_PROCESS_INIT;
> +	const unsigned char *bases[1] = {orig_tree};
> +	struct merge_options o;
> +	struct commit *result;
> +	char *his_tree_name;
>  	int status;
>  
> -	cp.git_cmd = 1;
> +	init_merge_options(&o);
> +
> +	o.branch1 = "HEAD";
> +	his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
> +	o.branch2 = his_tree_name;
>  
> -	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
> -			 sha1_to_hex(his_tree), linelen(state->msg), state->msg);
>  	if (state->quiet)
> -		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
> +		o.verbosity = 0;
>  
> -	argv_array_push(&cp.args, "merge-recursive");
> -	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
> -	argv_array_push(&cp.args, "--");
> -	argv_array_push(&cp.args, sha1_to_hex(our_tree));
> -	argv_array_push(&cp.args, sha1_to_hex(his_tree));
> +	status = merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result);
> +	if (status < 0)
> +		exit(128);
> +	free(his_tree_name);
>  
> -	status = run_command(&cp) ? (-1) : 0;
> -	discard_cache();
> -	read_cache();
>  	return status;
>  }

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
  2016-06-29 15:11   ` Johannes Schindelin
@ 2016-06-29 18:12   ` Eric Sunshine
  2016-06-30  8:41     ` Johannes Schindelin
  2016-06-29 20:50   ` Junio C Hamano
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 262+ messages in thread
From: Eric Sunshine @ 2016-06-29 18:12 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Git List, Junio C Hamano

On Wed, Jun 29, 2016 at 7:36 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> The vast majority of error messages in Git's source code which report a
> bug use the convention to prefix the message with "BUG:".
> [...]
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/merge-recursive.c b/merge-recursive.c
> @@ -1853,7 +1852,7 @@ int merge_trees(struct merge_options *o,
> -                               die(_("Unprocessed path??? %s"),
> +                               die(_("BUG: unprocessed path??? %s"),

This and others downcase the first word (which is consistent with
modern practice)...

> diff --git a/sha1_file.c b/sha1_file.c
> @@ -795,7 +795,7 @@ void close_all_packs(void)
> -                       die("BUG! Want to close pack marked 'do-not-close'");
> +                       die("BUG: Want to close pack marked 'do-not-close'");

...but this one neglects to.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-06-29 11:36 ` [PATCH 2/9] merge-recursive: clarify code in was_tracked() Johannes Schindelin
@ 2016-06-29 18:35   ` Junio C Hamano
  2016-07-01  9:23     ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 18:35 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> It can be puzzling to see that was_tracked() tries to match an index
> entry by name even if cache_name_pos() returned a negative value. Let's
> clarify that cache_name_pos() implicitly looks for stage 0, while we are
> also okay with finding other stages.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  merge-recursive.c | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/merge-recursive.c b/merge-recursive.c
> index 98f4632..bcb53f0 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -658,6 +658,7 @@ static int was_tracked(const char *path)
>  {
>  	int pos = cache_name_pos(path, strlen(path));
>  
> +	/* cache_name_pos() looks for stage == 0, so pos may be < 0 */

It returns >= if found at stage #0, or a negative (counting from -1)
to indicate where the path would be inserted if it were to be added
at stage #0.

The new comment does not explain how "pos may be < 0" leads to
"hence pos = -1 - pos is the right thing to do here".  It is
misleading and we probably are better off without.

>  	if (pos < 0)
>  		pos = -1 - pos;
>  	while (pos < active_nr &&

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 3/9] Prepare the builtins for a libified merge_recursive()
  2016-06-29 11:36 ` [PATCH 3/9] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
@ 2016-06-29 18:56   ` Junio C Hamano
  2016-07-01 10:14     ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 18:56 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> A truly libified function does not die() just for fun.

The sentence is wasting bits.  After all, a helper function in
run-once-and-exit program does not die() just for fun, either.

So what's more interesting to know for the readers?

> As such, the
> recursive merge will convert all die() calls to return -1 instead in the
> next commits, giving the caller a chance at least to print some helpful
> message.
>
> Let's prepare the builtins for this fatal error condition, even if we do
> not really do more than imitating the previous die()'s exit(128): this is
> what callers of e.g. `git merge` have come to expect.

One thing missing is your design decision and justification.

For example, the above explanation hints that the original code in
this hunk expected merge_trees() to die() with some useful message
when there is an error condition, but merge_trees() is going to be
improved not to die() itself and return -1 instead to signal an
error, so that the caller can react more flexibly, and this is a
step to prepare for the version of merge_trees() that no longer
dies.

> -			merge_trees(&o, new->commit->tree, work,
> +			ret = merge_trees(&o, new->commit->tree, work,
>  				old->commit->tree, &result);
> +			if (ret < 0)
> +				exit(128);

The postimage of the patch tells us that the caller is now
responsible for exiting with status 128, but neither the proposed
log message nor the above hunk tells us where the message the
original code must have given to the end user from die() inside
merge_trees().  The updated caller just exits, so a natural guess is
that the calls to die() have been changed to fprintf(stderr) with
the patch.

But that does not mesh very well with the stated objective of the
patch.  The callers want flexibility to do their own error handling,
including giving their own message, so letting merge_trees() to
still write the same message to the standard error stream would not
work well for them.  A caller may want to do merge_trees() just to
see if it succeeds, without wanting to give _any_ indication of that
is happening to the user, because it has an alternate/fallback code
if merge_trees() fails, for example (analogy: "am -3" first tries a
straight patch application before fallking back to 3-way merge; it
may not want to show the error from the first attempt).

The reader _can_ guess that this step ignores the error-message
issue, and improving it later (or keep ignoring that issue) might be
OK in the context of this patch series, but it is necessary to be
upfront to the readers what the design choices were and which one of
those choices the proposed patch adopted as its design for them to
be able to evaluate the patch series correctly.

One design alternative we've seen used in our code to help callers
who want no default messages is to pass struct strbuf &err down the
callchain and collect the default messages without emitting them
directly to the standard error stream when an error is diagnosed.  I
do not know if that pattern is applicable or should be applied to
this codepath.

> Note that the callers of the sequencer (revert and cherry-pick) already
> fail fast even for the return value -1; The only difference is that they
> now get a chance to say "<command> failed".
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  builtin/checkout.c | 4 +++-
>  builtin/merge.c    | 4 ++++
>  sequencer.c        | 4 ++++
>  3 files changed, 11 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index c3486bd..14312f7 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -567,8 +567,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
>  			o.ancestor = old->name;
>  			o.branch1 = new->name;
>  			o.branch2 = "local";
> -			merge_trees(&o, new->commit->tree, work,
> +			ret = merge_trees(&o, new->commit->tree, work,
>  				old->commit->tree, &result);
> +			if (ret < 0)
> +				exit(128);
>  			ret = reset_tree(new->commit->tree, opts, 0,
>  					 writeout_error);
>  			if (ret)
> diff --git a/builtin/merge.c b/builtin/merge.c
> index b555a1b..133b853 100644
> --- a/builtin/merge.c
> +++ b/builtin/merge.c
> @@ -682,6 +682,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
>  		hold_locked_index(&lock, 1);
>  		clean = merge_recursive(&o, head,
>  				remoteheads->item, reversed, &result);
> +		if (clean < 0)
> +			exit(128);
>  		if (active_cache_changed &&
>  		    write_locked_index(&the_index, &lock, COMMIT_LOCK))
>  			die (_("unable to write %s"), get_index_file());
> @@ -1550,6 +1552,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  		ret = try_merge_strategy(use_strategies[i]->name,
>  					 common, remoteheads,
>  					 head_commit, head_arg);
> +		if (ret < 0)
> +			exit(128);
>  		if (!option_commit && !ret) {
>  			merge_was_ok = 1;
>  			/*
> diff --git a/sequencer.c b/sequencer.c
> index c6362d6..13b794a 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -293,6 +293,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
>  	clean = merge_trees(&o,
>  			    head_tree,
>  			    next_tree, base_tree, &result);
> +	if (clean < 0)
> +		return clean;
>  
>  	if (active_cache_changed &&
>  	    write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
> @@ -561,6 +563,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
>  	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
>  		res = do_recursive_merge(base, next, base_label, next_label,
>  					 head, &msgbuf, opts);
> +		if (res < 0)
> +			return res;
>  		write_message(&msgbuf, git_path_merge_msg());
>  	} else {
>  		struct commit_list *common = NULL;

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 4/9] merge_recursive: abort properly upon errors
  2016-06-29 11:36 ` [PATCH 4/9] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-06-29 20:08   ` Junio C Hamano
  2016-07-01 10:16     ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 20:08 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> There are a couple of places where return values indicating errors
> are ignored. Let's teach them manners.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  merge-recursive.c | 10 ++++++++--
>  1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/merge-recursive.c b/merge-recursive.c
> index bcb53f0..c4ece96 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -1944,8 +1944,9 @@ int merge_recursive(struct merge_options *o,
>  		saved_b2 = o->branch2;
>  		o->branch1 = "Temporary merge branch 1";
>  		o->branch2 = "Temporary merge branch 2";
> -		merge_recursive(o, merged_common_ancestors, iter->item,
> -				NULL, &merged_common_ancestors);
> +		if (merge_recursive(o, merged_common_ancestors, iter->item,
> +				NULL, &merged_common_ancestors) < 0)
> +			return -1;
>  		o->branch1 = saved_b1;
>  		o->branch2 = saved_b2;
>  		o->call_depth--;

OK, this early return (and others in this patch) are only for
negative (i.e. error) cases, and "attempted a merge, resulted in
conflicts" cases are handled as before.

Which is good.

I wonder if o->branch[12] need to be restored, though.  The only
sensible thing the caller can do is to punt, but would it expect to
be able to do some error reporting based on these fields, e.g.
printf("merge of %s and %s failed", o->branch1, o->branch2) or
something?  In addition to that kind of "state restoration", we may
need to watch out for resource leaks, but I think there is none at
least these three early returns.

Thanks.

> @@ -1961,6 +1962,8 @@ int merge_recursive(struct merge_options *o,
>  	o->ancestor = "merged common ancestors";
>  	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
>  			    &mrtree);
> +	if (clean < 0)
> +		return clean;
>  
>  	if (o->call_depth) {
>  		*result = make_virtual_commit(mrtree, "merged tree");
> @@ -2017,6 +2020,9 @@ int merge_recursive_generic(struct merge_options *o,
>  	hold_locked_index(lock, 1);
>  	clean = merge_recursive(o, head_commit, next_commit, ca,
>  			result);
> +	if (clean < 0)
> +		return clean;
> +
>  	if (active_cache_changed &&
>  	    write_locked_index(&the_index, lock, COMMIT_LOCK))
>  		return error(_("Unable to write index."));

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 5/9] merge-recursive: avoid returning a wholesale struct
  2016-06-29 11:36 ` [PATCH 5/9] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
@ 2016-06-29 20:21   ` Junio C Hamano
  2016-07-01 13:48     ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 20:21 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> It is technically allowed, as per C89, for functions' return type to
> be complete structs (i.e. *not* just pointers to structs), but it is
> bad practice.

Not necessarily.

> This is a very late attempt to contain the damage done by this developer
> in 6d297f8 (Status update on merge-recursive in C, 2006-07-08) which
> introduced such a return type.
>
> It will also help the current effort to libify merge-recursive.c, as
> it will allow us to return proper error codes later.

But this part of the motivation does make sense.  Having to pass an
extra int &error_code field, only because the return value is
already used for something else, is a lot more weird.

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  merge-recursive.c | 93 ++++++++++++++++++++++++++++++-------------------------
>  1 file changed, 50 insertions(+), 43 deletions(-)
>
> diff --git a/merge-recursive.c b/merge-recursive.c
> index c4ece96..d56651c9 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -888,47 +888,47 @@ static int merge_3way(struct merge_options *o,
>  	return merge_status;
>  }
>  
> -static struct merge_file_info merge_file_1(struct merge_options *o,
> +static int merge_file_1(struct merge_options *o,
>  					   const struct diff_filespec *one,
>  					   const struct diff_filespec *a,
>  					   const struct diff_filespec *b,
>  					   const char *branch1,
> -					   const char *branch2)
> +					   const char *branch2,
> +					   struct merge_file_info *result)
>  {
> -	struct merge_file_info result;
> -	result.merge = 0;
> -	result.clean = 1;
> +	result->merge = 0;
> +	result->clean = 1;
>  
>  	if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
> -		result.clean = 0;
> +		result->clean = 0;
>  		if (S_ISREG(a->mode)) {
> -			result.mode = a->mode;
> -			hashcpy(result.sha, a->sha1);
> +			result->mode = a->mode;
> +			hashcpy(result->sha, a->sha1);
>  		} else {
> -			result.mode = b->mode;
> -			hashcpy(result.sha, b->sha1);
> +			result->mode = b->mode;
> +			hashcpy(result->sha, b->sha1);
>  		}
>  	} else {
>  		if (!sha_eq(a->sha1, one->sha1) && !sha_eq(b->sha1, one->sha1))
> -			result.merge = 1;
> +			result->merge = 1;
>  
>  		/*
>  		 * Merge modes
>  		 */
>  		if (a->mode == b->mode || a->mode == one->mode)
> -			result.mode = b->mode;
> +			result->mode = b->mode;
>  		else {
> -			result.mode = a->mode;
> +			result->mode = a->mode;
>  			if (b->mode != one->mode) {
> -				result.clean = 0;
> -				result.merge = 1;
> +				result->clean = 0;
> +				result->merge = 1;
>  			}
>  		}
>  
>  		if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, one->sha1))
> -			hashcpy(result.sha, b->sha1);
> +			hashcpy(result->sha, b->sha1);
>  		else if (sha_eq(b->sha1, one->sha1))
> -			hashcpy(result.sha, a->sha1);
> +			hashcpy(result->sha, a->sha1);
>  		else if (S_ISREG(a->mode)) {
>  			mmbuffer_t result_buf;
>  			int merge_status;
> @@ -940,62 +940,63 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
>  				die(_("Failed to execute internal merge"));
>  
>  			if (write_sha1_file(result_buf.ptr, result_buf.size,
> -					    blob_type, result.sha))
> +					    blob_type, result->sha))
>  				die(_("Unable to add %s to database"),
>  				    a->path);
>  
>  			free(result_buf.ptr);
> -			result.clean = (merge_status == 0);
> +			result->clean = (merge_status == 0);
>  		} else if (S_ISGITLINK(a->mode)) {
> -			result.clean = merge_submodule(result.sha,
> +			result->clean = merge_submodule(result->sha,
>  						       one->path, one->sha1,
>  						       a->sha1, b->sha1,
>  						       !o->call_depth);
>  		} else if (S_ISLNK(a->mode)) {
> -			hashcpy(result.sha, a->sha1);
> +			hashcpy(result->sha, a->sha1);
>  
>  			if (!sha_eq(a->sha1, b->sha1))
> -				result.clean = 0;
> +				result->clean = 0;
>  		} else
>  			die(_("BUG: unsupported object type in the tree"));
>  	}
>  
> -	return result;
> +	return 0;
>  }
>  
> -static struct merge_file_info
> -merge_file_special_markers(struct merge_options *o,
> +static int merge_file_special_markers(struct merge_options *o,
>  			   const struct diff_filespec *one,
>  			   const struct diff_filespec *a,
>  			   const struct diff_filespec *b,
>  			   const char *branch1,
>  			   const char *filename1,
>  			   const char *branch2,
> -			   const char *filename2)
> +			   const char *filename2,
> +			   struct merge_file_info *mfi)
>  {
>  	char *side1 = NULL;
>  	char *side2 = NULL;
> -	struct merge_file_info mfi;
> +	int ret;
>  
>  	if (filename1)
>  		side1 = xstrfmt("%s:%s", branch1, filename1);
>  	if (filename2)
>  		side2 = xstrfmt("%s:%s", branch2, filename2);
>  
> -	mfi = merge_file_1(o, one, a, b,
> -			   side1 ? side1 : branch1, side2 ? side2 : branch2);
> +	ret = merge_file_1(o, one, a, b,
> +		side1 ? side1 : branch1, side2 ? side2 : branch2, mfi);
>  	free(side1);
>  	free(side2);
> -	return mfi;
> +	return ret;
>  }
>  
> -static struct merge_file_info merge_file_one(struct merge_options *o,
> +static int merge_file_one(struct merge_options *o,
>  					 const char *path,
>  					 const unsigned char *o_sha, int o_mode,
>  					 const unsigned char *a_sha, int a_mode,
>  					 const unsigned char *b_sha, int b_mode,
>  					 const char *branch1,
> -					 const char *branch2)
> +					 const char *branch2,
> +					 struct merge_file_info *mfi)
>  {
>  	struct diff_filespec one, a, b;
>  
> @@ -1006,7 +1007,7 @@ static struct merge_file_info merge_file_one(struct merge_options *o,
>  	a.mode = a_mode;
>  	hashcpy(b.sha1, b_sha);
>  	b.mode = b_mode;
> -	return merge_file_1(o, &one, &a, &b, branch1, branch2);
> +	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
>  }
>  
>  static void handle_change_delete(struct merge_options *o,
> @@ -1179,11 +1180,14 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
>  		struct merge_file_info mfi;
>  		struct diff_filespec other;
>  		struct diff_filespec *add;
> -		mfi = merge_file_one(o, one->path,
> +
> +		if (merge_file_one(o, one->path,
>  				 one->sha1, one->mode,
>  				 a->sha1, a->mode,
>  				 b->sha1, b->mode,
> -				 ci->branch1, ci->branch2);
> +				 ci->branch1, ci->branch2, &mfi) < 0)
> +			return;
> +
>  		/*
>  		 * FIXME: For rename/add-source conflicts (if we could detect
>  		 * such), this is wrong.  We should instead find a unique
> @@ -1237,12 +1241,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
>  	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
>  	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
>  
> -	mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
> +	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
>  					    o->branch1, c1->path,
> -					    o->branch2, ci->ren1_other.path);
> -	mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
> +					    o->branch2, ci->ren1_other.path, &mfi_c1) < 0 ||
> +	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
>  					    o->branch1, ci->ren2_other.path,
> -					    o->branch2, c2->path);
> +					    o->branch2, c2->path, &mfi_c2) < 0)
> +		return;
>  
>  	if (o->call_depth) {
>  		/*
> @@ -1463,10 +1468,11 @@ static int process_renames(struct merge_options *o,
>  				       ren1_dst, branch2);
>  				if (o->call_depth) {
>  					struct merge_file_info mfi;
> -					mfi = merge_file_one(o, ren1_dst, null_sha1, 0,
> +					if (merge_file_one(o, ren1_dst, null_sha1, 0,
>  							 ren1->pair->two->sha1, ren1->pair->two->mode,
>  							 dst_other.sha1, dst_other.mode,
> -							 branch1, branch2);
> +							 branch1, branch2, &mfi) < 0)
> +						return -1;
>  					output(o, 1, _("Adding merged %s"), ren1_dst);
>  					update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
>  					try_merge = 0;
> @@ -1622,9 +1628,10 @@ static int merge_content(struct merge_options *o,
>  		if (dir_in_way(path, !o->call_depth))
>  			df_conflict_remains = 1;
>  	}
> -	mfi = merge_file_special_markers(o, &one, &a, &b,
> +	if (merge_file_special_markers(o, &one, &a, &b,
>  					 o->branch1, path1,
> -					 o->branch2, path2);
> +					 o->branch2, path2, &mfi) < 0)
> +		return -1;
>  
>  	if (mfi.clean && !df_conflict_remains &&
>  	    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
  2016-06-29 15:11   ` Johannes Schindelin
  2016-06-29 18:12   ` Eric Sunshine
@ 2016-06-29 20:50   ` Junio C Hamano
  2016-06-30  8:42     ` Johannes Schindelin
  2016-06-30  5:38   ` Johannes Sixt
  2016-07-02  5:11   ` Duy Nguyen
  4 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 20:50 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> The vast majority of error messages in Git's source code which report a
> bug use the convention to prefix the message with "BUG:".

Good thing to do.

But if we were to review and apply a 200+ line patch, I wonder if we
want to go one step further to allow us to write

    BUG("killed-file %s not found", name);

instead.


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 7/9] merge-recursive: handle return values indicating errors
  2016-06-29 11:37 ` [PATCH 7/9] merge-recursive: handle return values indicating errors Johannes Schindelin
@ 2016-06-29 21:06   ` Junio C Hamano
  2016-07-01 11:08     ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 21:06 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> diff --git a/merge-recursive.c b/merge-recursive.c
> index 6ab7dfc..bb075e3 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -266,8 +266,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
>  		active_cache_tree = cache_tree();
>  
>  	if (!cache_tree_fully_valid(active_cache_tree) &&
> -	    cache_tree_update(&the_index, 0) < 0)
> -		die(_("error building trees"));
> +	    cache_tree_update(&the_index, 0) < 0) {
> +		error(_("error building trees"));
> +		return NULL;
> +	}

This actually is a BUG(), isn't it?  We have already verified that
the cache is merged, so cache_tree_update() ought to be able to come
up with the whole-tree hash.

> @@ -548,19 +550,17 @@ static int update_stages(const char *path, const struct diff_filespec *o,
>  	 */
>  	int clear = 1;
>  	int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
> +	int ret = 0;
> +
>  	if (clear)
> -		if (remove_file_from_cache(path))
> -			return -1;
> -	if (o)
> -		if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
> -			return -1;
> -	if (a)
> -		if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
> -			return -1;
> -	if (b)
> -		if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
> -			return -1;
> -	return 0;
> +		ret = remove_file_from_cache(path);
> +	if (!ret && o)
> +		ret = add_cacheinfo(o->mode, o->sha1, path, 1, 0, options);
> +	if (!ret && a)
> +		ret = add_cacheinfo(a->mode, a->sha1, path, 2, 0, options);
> +	if (!ret && b)
> +		ret = add_cacheinfo(b->mode, b->sha1, path, 3, 0, options);
> +	return ret;
>  }

Aren't the preimage and the postimage doing the same thing?  The
only two differences I spot are (1) it is clear in the original that
the returned value is -1 in the error case, even if the error
convention of remove_file_from_cache() and add_cacheinfo() were "0
is good, others are bad"; and (2) the control flow is easier to
follow in the original.

> @@ -736,7 +736,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
>  	return error(msg, path, _(": perhaps a D/F conflict?"));
>  }
>  
> -static void update_file_flags(struct merge_options *o,
> +static int update_file_flags(struct merge_options *o,
>  			      const unsigned char *sha,
>  			      unsigned mode,
>  			      const char *path,
> @@ -777,8 +777,7 @@ static void update_file_flags(struct merge_options *o,
>  
>  		if (make_room_for_path(o, path) < 0) {
>  			update_wd = 0;
> -			free(buf);
> -			goto update_index;
> +			goto free_buf;
>  		}
>  		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
>  			int fd;
> @@ -801,20 +800,22 @@ static void update_file_flags(struct merge_options *o,
>  		} else
>  			die(_("do not know what to do with %06o %s '%s'"),
>  			    mode, sha1_to_hex(sha), path);
> +free_buf:
>  		free(buf);

I somehow find the above change harder to follow than the original.

>  	}
>   update_index:
>  	if (update_cache)
>  		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
> +	return 0;
>  }
>  

This one is in line with the stated goal of the patch.

> @@ -1028,21 +1030,23 @@ static void handle_change_delete(struct merge_options *o,
>  		 * correct; since there is no true "middle point" between
>  		 * them, simply reuse the base version for virtual merge base.
>  		 */
> -		remove_file_from_cache(path);
> -		update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
> +		ret = remove_file_from_cache(path);
> +		if (!ret)
> +			ret = update_file(o, 0, o_sha, o_mode,
> +					  renamed ? renamed : path);

As you noted in the log message, this does change the behaviour.  If
remove returns non-zero for whatever reason, we still did update()
in the original, but we no longer do.  Does this have negative
effect to the overall codeflow?

Or, assuming that everybody returns -1 for errors, perhaps

	ret = remove();
        ret |= update();

may be a more faithful and safe conversion?

> @@ -1087,21 +1094,22 @@ static void conflict_rename_delete(struct merge_options *o,
>  		b_mode = dest->mode;
>  	}
>  
> -	handle_change_delete(o,
> +	ret = handle_change_delete(o,
>  			     o->call_depth ? orig->path : dest->path,
>  			     orig->sha1, orig->mode,
>  			     a_sha, a_mode,
>  			     b_sha, b_mode,
>  			     _("rename"), _("renamed"));
> -
> -	if (o->call_depth) {
> -		remove_file_from_cache(dest->path);
> -	} else {
> -		update_stages(dest->path, NULL,
> +	if (ret < 0)
> +		return ret;
> +	if (o->call_depth)
> +		ret = remove_file_from_cache(dest->path);
> +	else
> +		ret = update_stages(dest->path, NULL,
>  			      rename_branch == o->branch1 ? dest : NULL,
>  			      rename_branch == o->branch1 ? NULL : dest);
> -	}

Similarly, if handle_change_delete() returns non-zero, we no longer
call remove() or update().  Is that a good behaviour change?

> -static void handle_file(struct merge_options *o,
> +static int handle_file(struct merge_options *o,
>  			struct diff_filespec *rename,
>  			int stage,
>  			struct rename_conflict_info *ci)

Likewise.

> -static void conflict_rename_rename_1to2(struct merge_options *o,
> +static int conflict_rename_rename_1to2(struct merge_options *o,
>  					struct rename_conflict_info *ci)
>  {
> ...
> -		if (merge_file_one(o, one->path,
> +		if ((ret = merge_file_one(o, one->path,
>  				 one->sha1, one->mode,
>  				 a->sha1, a->mode,
>  				 b->sha1, b->mode,
> -				 ci->branch1, ci->branch2, &mfi) < 0)
> -			return;
> +				 ci->branch1, ci->branch2, &mfi)))
> +			return ret;

This does not change behaviour.

> @@ -1194,7 +1208,8 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
>  		 * pathname and then either rename the add-source file to that
>  		 * unique path, or use that unique path instead of src here.
>  		 */
> -		update_file(o, 0, mfi.sha, mfi.mode, one->path);
> +		if ((ret = update_file(o, 0, mfi.sha, mfi.mode, one->path)))
> +			return ret;

But this does.

> @@ -1205,22 +1220,31 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
>  		 * resolving the conflict at that path in its favor.
>  		 */
>  		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
> -		if (add)
> -			update_file(o, 0, add->sha1, add->mode, a->path);
> +		if (add) {
> +			if ((ret = update_file(o, 0, add->sha1, add->mode,
> +					a->path)))
> +				return ret;
> +		}

So does this.


>  		else
>  			remove_file_from_cache(a->path);
>  		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
> -		if (add)
> -			update_file(o, 0, add->sha1, add->mode, b->path);
> +		if (add) {
> +			if ((ret = update_file(o, 0, add->sha1, add->mode,
> +					b->path)))
> +				return ret;
> +		}

And this.

>  	} else {
> -		handle_file(o, a, 2, ci);
> -		handle_file(o, b, 3, ci);
> +		if ((ret = handle_file(o, a, 2, ci)) ||
> +		    (ret = handle_file(o, b, 3, ci)))
> +			return ret;

And this.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 8/9] merge-recursive: switch to returning errors instead of dying
  2016-06-29 11:37 ` [PATCH 8/9] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
@ 2016-06-29 21:19   ` Junio C Hamano
  2016-07-01 11:14     ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 21:19 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> @@ -743,6 +741,8 @@ static int update_file_flags(struct merge_options *o,
>  			      int update_cache,
>  			      int update_wd)
> ...
> +			ret = error_errno(_("do not know what to do with %06o %s '%s'"),
> +				mode, sha1_to_hex(sha), path);
>  free_buf:

OK, with a few more users of this label, it no longer looks so
strange to me to have this label here.

At least, match the indentation level to the existing one we see
below, though?

>  		free(buf);
>  	}
>   update_index:
> -	if (update_cache)
> +	if (!ret && update_cache)
>  		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);

Unlike my reaction to "if (!ret) do this" in an earlier patch, I do
think this change is sensible.  We wouldn't have reached here in the
original code if we saw errors, and some of the variable used to
call add_cacheinfo() at this point may be garbage when ret is
non-zero, i.e. we know we earlier saw an error.

> -	return 0;
> +	return ret;
>  }
> ...
>  			if ((merge_status < 0) || !result_buf.ptr)
> -				die(_("Failed to execute internal merge"));
> +				ret = error(_("Failed to execute internal merge"));
>  
> -			if (write_sha1_file(result_buf.ptr, result_buf.size,
> +			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
>  					    blob_type, result->sha))

Likewise.

> @@ -1861,7 +1867,7 @@ static int process_entry(struct merge_options *o,
>  		 */
>  		remove_file(o, 1, path, !a_mode);
>  	} else
> -		die(_("Fatal merge failure, shouldn't happen."));
> +		return error(_("Fatal merge failure, shouldn't happen."));

Isn't this BUG()?


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 9/9] am: make a direct call to merge_recursive
  2016-06-29 11:38 ` [PATCH 9/9] am: make a direct call to merge_recursive Johannes Schindelin
  2016-06-29 17:45   ` Junio C Hamano
@ 2016-06-29 21:23   ` Junio C Hamano
  2016-07-01 12:46     ` Johannes Schindelin
  1 sibling, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-06-29 21:23 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> -	cp.git_cmd = 1;
> +	init_merge_options(&o);
> +
> +	o.branch1 = "HEAD";
> +	his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
> +	o.branch2 = his_tree_name;
>  
> -	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
> -			 sha1_to_hex(his_tree), linelen(state->msg), state->msg);
>  	if (state->quiet)
> -		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
> +		o.verbosity = 0;
>  
> -	argv_array_push(&cp.args, "merge-recursive");
> -	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
> -	argv_array_push(&cp.args, "--");
> -	argv_array_push(&cp.args, sha1_to_hex(our_tree));
> -	argv_array_push(&cp.args, sha1_to_hex(his_tree));
> +	status = merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result);
> +	if (status < 0)
> +		exit(128);
> +	free(his_tree_name);
>  
> -	status = run_command(&cp) ? (-1) : 0;
> -	discard_cache();
> -	read_cache();
>  	return status;
>  }

Is this a correct conversion?

We used to prepare the command line and called run_command() and
without dying returned status with the error status from the
merge-recursive that was spawned by run_command().

The new code does not report failure to the caller and instead dies.


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
                     ` (2 preceding siblings ...)
  2016-06-29 20:50   ` Junio C Hamano
@ 2016-06-30  5:38   ` Johannes Sixt
  2016-06-30  8:45     ` Johannes Schindelin
  2016-07-02  5:11   ` Duy Nguyen
  4 siblings, 1 reply; 262+ messages in thread
From: Johannes Sixt @ 2016-06-30  5:38 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Junio C Hamano

Am 29.06.2016 um 13:36 schrieb Johannes Schindelin:
> @@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
>
>   			if (!sha_eq(a->sha1, b->sha1))
>   				result.clean = 0;
> -		} else {
> -			die(_("unsupported object type in the tree"));
> -		}
> +		} else
> +			die(_("BUG: unsupported object type in the tree"));

Would it perhaps make sense to remove the _() markup (here and a few 
more instances in this patch)? It's simpler for developers to find the 
code location when a "BUG:" is reported untranslated.

-- Hannes


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 9/9] am: make a direct call to merge_recursive
  2016-06-29 17:45   ` Junio C Hamano
@ 2016-06-30  8:38     ` Johannes Schindelin
  2016-07-01 16:03       ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-30  8:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > From: Junio C Hamano <gitster@pobox.com>
> 
> Did I write this thing?

Yes, you did. It was db05d6194d3f9ea9e64163944961d5f6e85302be as part of
pu@{2016-06-15}.

> Having two sets of numbers that illustrated that this is not really
> a useful optimization in the bigger picture looks vaguely familiar
> (e.g. $gmane/279417), but the numbers are different.

I beg to differ. While the performance improvement is not huge, even your
toy experiment shows it is significant, i.e. noticeable.

> > 	It feels *slightly* wrong to submit your own patch to review,
> > 	however, please keep in mind that
> >
> > 	1) I changed the patch (o.gently does not exist anymore, so I do
> > 	   not set it), and
> >
> > 	2) I added my own timings performed on Windows.
> 
> It probably is much less confusing if you take the authorship,
> possibly with a passing reference to whatever I wrote as the source
> of inspiration in the log message, or something.  I do not think I
> deserve a credit in this 9-patch series.

The patch is an almost verbatim copy of db05d6194, I only removed the
now-obsolete "gently" setting, is all.

That means that I feel really, really uneasy about claiming authorship
because I did not write it.

If you really want me to, I will take custody of this patch and rewrite
the commit message as well using --reset--author, of course.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 18:12   ` Eric Sunshine
@ 2016-06-30  8:41     ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-30  8:41 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Junio C Hamano

Hi Eric,

On Wed, 29 Jun 2016, Eric Sunshine wrote:

> On Wed, Jun 29, 2016 at 7:36 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > The vast majority of error messages in Git's source code which report a
> > bug use the convention to prefix the message with "BUG:".
> > [...]
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/merge-recursive.c b/merge-recursive.c
> > @@ -1853,7 +1852,7 @@ int merge_trees(struct merge_options *o,
> > -                               die(_("Unprocessed path??? %s"),
> > +                               die(_("BUG: unprocessed path??? %s"),
> 
> This and others downcase the first word (which is consistent with
> modern practice)...
> 
> > diff --git a/sha1_file.c b/sha1_file.c
> > @@ -795,7 +795,7 @@ void close_all_packs(void)
> > -                       die("BUG! Want to close pack marked 'do-not-close'");
> > +                       die("BUG: Want to close pack marked 'do-not-close'");
> 
> ...but this one neglects to.

Thanks! Will be fixed as part of v2.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 20:50   ` Junio C Hamano
@ 2016-06-30  8:42     ` Johannes Schindelin
  2016-06-30  9:23       ` Jeff King
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-30  8:42 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > The vast majority of error messages in Git's source code which report a
> > bug use the convention to prefix the message with "BUG:".
> 
> Good thing to do.
> 
> But if we were to review and apply a 200+ line patch, I wonder if we
> want to go one step further to allow us to write
> 
>     BUG("killed-file %s not found", name);
> 
> instead.

If the idea is to make it easier to find, I would wager a guess that
'die("BUG:' would be just as good a search term. Even better, I think,
because 'BUG' would also match comments.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-30  5:38   ` Johannes Sixt
@ 2016-06-30  8:45     ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-06-30  8:45 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: git, Junio C Hamano

Hi Hannes,

On Thu, 30 Jun 2016, Johannes Sixt wrote:

> Am 29.06.2016 um 13:36 schrieb Johannes Schindelin:
> > @@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct
> > merge_options *o,
> >
> >      if (!sha_eq(a->sha1, b->sha1))
> >   				result.clean = 0;
> > -		} else {
> > -			die(_("unsupported object type in the tree"));
> > -		}
> > +		} else
> > +			die(_("BUG: unsupported object type in the tree"));
> 
> Would it perhaps make sense to remove the _() markup (here and a few more
> instances in this patch)? It's simpler for developers to find the code
> location when a "BUG:" is reported untranslated.

I would agree, but the purpose of this patch was not to fix that, but to
fix the inconsistency of the message.

Maybe as an add-on patch, with *all* 'die(_("BUG:' instances converted?
That would be even more outside the purview of my patch series than
touching the bug reports outside merge-recursive.c, though.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-30  8:42     ` Johannes Schindelin
@ 2016-06-30  9:23       ` Jeff King
  2016-07-01 13:51         ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Jeff King @ 2016-06-30  9:23 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, git

On Thu, Jun 30, 2016 at 10:42:37AM +0200, Johannes Schindelin wrote:

> > > The vast majority of error messages in Git's source code which report a
> > > bug use the convention to prefix the message with "BUG:".
> > 
> > Good thing to do.
> > 
> > But if we were to review and apply a 200+ line patch, I wonder if we
> > want to go one step further to allow us to write
> > 
> >     BUG("killed-file %s not found", name);
> > 
> > instead.
> 
> If the idea is to make it easier to find, I would wager a guess that
> 'die("BUG:' would be just as good a search term. Even better, I think,
> because 'BUG' would also match comments.

I have been tempted to switch to BUG(), because it would make it easy to
call abort() and get a coredump (and therefore a stack trace). On the
other hand:

  - we could always trigger such behavior in die() by looking for "BUG:" in
    the output string. :)

  - it's also sometimes useful to get a stack trace from a regular
    non-bug die(). So maybe something optional like:

      if (git_env_bool("GIT_ABORT_ON_DIE", 0))
              abort();

    would be helpful (since you have to turn it on ahead of time, you
    could also just run the program under gdb, of course; however, I
    sometimes find that it's hard to get gdb where you want it because
    git spawns so many sub-programs. Or maybe I just need to get better
    at using gdb's child-following options).

The other thing BUG() would get us is that we could turn it into a macro
(on systems with vararg macros) and report things like __FILE__ and
__LINE__.  In practice, though our BUG messages are unique enough that
there is no problem finding the source.

-Peff

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-06-29 18:35   ` Junio C Hamano
@ 2016-07-01  9:23     ` Johannes Schindelin
  2016-07-01 15:31       ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01  9:23 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > It can be puzzling to see that was_tracked() tries to match an index
> > entry by name even if cache_name_pos() returned a negative value. Let's
> > clarify that cache_name_pos() implicitly looks for stage 0, while we are
> > also okay with finding other stages.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  merge-recursive.c | 1 +
> >  1 file changed, 1 insertion(+)
> >
> > diff --git a/merge-recursive.c b/merge-recursive.c
> > index 98f4632..bcb53f0 100644
> > --- a/merge-recursive.c
> > +++ b/merge-recursive.c
> > @@ -658,6 +658,7 @@ static int was_tracked(const char *path)
> >  {
> >  	int pos = cache_name_pos(path, strlen(path));
> >  
> > +	/* cache_name_pos() looks for stage == 0, so pos may be < 0 */
> 
> It returns >= if found at stage #0, or a negative (counting from -1)
> to indicate where the path would be inserted if it were to be added
> at stage #0.

Right. Please note that nothing in the function call to cache_name_pos()
specifies that we are looking for any particular stage. You are probably
too intimate with the implementation details, so naturally you assume that
cache_name_pos() looks for a precise match *with stage 0*. I am not that
familiar with that part of the implementation, so I *was* puzzled.

> The new comment does not explain how "pos may be < 0" leads to
> "hence pos = -1 - pos is the right thing to do here".  It is
> misleading and we probably are better off without.

I agree that the comment is not very good currently. But I disagree that
we are better off without any comment here.

So maybe a comment is not really enough here. In case the entry is in
stage 0, we will have an exact match, so there is no need to go to the
lengths of a while() loop.

I would like to propose this diff instead (it is larger, but with a net
savings of one line):

-- snipsnap --
diff --git a/merge-recursive.c b/merge-recursive.c
index d5a593c..0eda51a 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -658,24 +658,22 @@ static int was_tracked(const char *path)
 {
 	int pos = cache_name_pos(path, strlen(path));
 
-	if (pos < 0)
-		pos = -1 - pos;
-	while (pos < active_nr &&
-	       !strcmp(path, active_cache[pos]->name)) {
+	if (pos >= 0)
+		return pos < active_nr;
+	/*
+	 * cache_name_pos() looks for stage == 0, even if we did not ask for
+	 * it. Let's look for stage == 2 now.
+	 */
+	for (pos = -1 - pos; pos < active_nr &&
+	     !strcmp(path, active_cache[pos]->name); pos++)
 		/*
 		 * If stage #0, it is definitely tracked.
 		 * If it has stage #2 then it was tracked
 		 * before this merge started.  All other
 		 * cases the path was not tracked.
 		 */
-		switch (ce_stage(active_cache[pos])) {
-		case 0:
-		case 2:
+		if (ce_stage(active_cache[pos]) == 2)
 			return 1;
-		}
-		pos++;
-	}
 	return 0;
 }
 


^ permalink raw reply related	[flat|nested] 262+ messages in thread

* Re: [PATCH 3/9] Prepare the builtins for a libified merge_recursive()
  2016-06-29 18:56   ` Junio C Hamano
@ 2016-07-01 10:14     ` Johannes Schindelin
  2016-07-01 15:43       ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 10:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > A truly libified function does not die() just for fun.
> 
> The sentence is wasting bits.  After all, a helper function in
> run-once-and-exit program does not die() just for fun, either.

Given that I had a hard time when reviewing Christian's apply patches to
drive home the message that it is not okay for library functions to call
die() or exit(), I happen to disagree with your notion that this sentence
is wasting bits.

This sentence does not so much target *you* personally as audience, but
the occasional reader of the log who wonders: "Why don't we just call
die()? We would not have to worry about passing back the return value
through all those long call chains..."

> > As such, the recursive merge will convert all die() calls to return -1
> > instead in the next commits, giving the caller a chance at least to
> > print some helpful message.
> >
> > Let's prepare the builtins for this fatal error condition, even if we do
> > not really do more than imitating the previous die()'s exit(128): this is
> > what callers of e.g. `git merge` have come to expect.
> 
> One thing missing is your design decision and justification.
> 
> For example, the above explanation hints that the original code in
> this hunk expected merge_trees() to die() with some useful message
> when there is an error condition, but merge_trees() is going to be
> improved not to die() itself and return -1 instead to signal an
> error, so that the caller can react more flexibly, and this is a
> step to prepare for the version of merge_trees() that no longer
> dies.

Okay, I will replace the "As such..." paragraph with a modified version of
your paraphrased explanation.

> > -			merge_trees(&o, new->commit->tree, work,
> > +			ret = merge_trees(&o, new->commit->tree, work,
> >  				old->commit->tree, &result);
> > +			if (ret < 0)
> > +				exit(128);
> 
> The postimage of the patch tells us that the caller is now
> responsible for exiting with status 128, but neither the proposed
> log message nor the above hunk tells us where the message the
> original code must have given to the end user from die() inside
> merge_trees().  The updated caller just exits, so a natural guess is
> that the calls to die() have been changed to fprintf(stderr) with
> the patch.

Even more natural is it to guess that the code will call error(), just
like we do almost everywhere else.

But you are right, I do not have to leave the reader guessing. Better to
err on the side of being slightly verbose than to be so concise that
nobody understands what I mean.

> But that does not mesh very well with the stated objective of the
> patch.  The callers want flexibility to do their own error handling,
> including giving their own message, so letting merge_trees() to
> still write the same message to the standard error stream would not
> work well for them.  A caller may want to do merge_trees() just to
> see if it succeeds, without wanting to give _any_ indication of that
> is happening to the user, because it has an alternate/fallback code
> if merge_trees() fails, for example (analogy: "am -3" first tries a
> straight patch application before fallking back to 3-way merge; it
> may not want to show the error from the first attempt).
> 
> The reader _can_ guess that this step ignores the error-message
> issue, and improving it later (or keep ignoring that issue) might be
> OK in the context of this patch series, but it is necessary to be
> upfront to the readers what the design choices were and which one of
> those choices the proposed patch adopted as its design for them to
> be able to evaluate the patch series correctly.

To be honest, I did not even think about the error message issue because
my primary concern is to teach the sequencer to perform rebase -i's grunt
work. And while we usually suppress the output of the commands in rebase
-i, we do show them in case of errors.

It will make things more complex, unfortunately, even if it will be
straight-forward: there is already a strbuf and a flag in struct
merge_options to collect output. The merge_options are simply not passed
through to all of the previously die()ing functions yet.

I won't have time to get this implemented this week, unfortunately. So
please do not expect the next iteration of this patch series before next
week.

I could imagine that you wanted even more fine-grained control, where we
have a range of return values indicating different error conditions.
However, I already spent two weeks' worth of work to get this far, and
would like to defer that task to the developer who will actually need
these fine-grained return values (if we ever will need them).

Ciao,
Dscho

P.S.: For the future, would you mind deleting the quoted remainder of my
patches when there are no further comments? I deleted a footer of 73
unnecessary lines in this mail. It's no big deal if this is too tedious,
but it would make my life easier.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 4/9] merge_recursive: abort properly upon errors
  2016-06-29 20:08   ` Junio C Hamano
@ 2016-07-01 10:16     ` Johannes Schindelin
  2016-07-01 15:56       ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 10:16 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > There are a couple of places where return values indicating errors
> > are ignored. Let's teach them manners.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  merge-recursive.c | 10 ++++++++--
> >  1 file changed, 8 insertions(+), 2 deletions(-)
> >
> > diff --git a/merge-recursive.c b/merge-recursive.c
> > index bcb53f0..c4ece96 100644
> > --- a/merge-recursive.c
> > +++ b/merge-recursive.c
> > @@ -1944,8 +1944,9 @@ int merge_recursive(struct merge_options *o,
> >  		saved_b2 = o->branch2;
> >  		o->branch1 = "Temporary merge branch 1";
> >  		o->branch2 = "Temporary merge branch 2";
> > -		merge_recursive(o, merged_common_ancestors, iter->item,
> > -				NULL, &merged_common_ancestors);
> > +		if (merge_recursive(o, merged_common_ancestors, iter->item,
> > +				NULL, &merged_common_ancestors) < 0)
> > +			return -1;
> >  		o->branch1 = saved_b1;
> >  		o->branch2 = saved_b2;
> >  		o->call_depth--;
> 
> OK, this early return (and others in this patch) are only for
> negative (i.e. error) cases, and "attempted a merge, resulted in
> conflicts" cases are handled as before.
> 
> Which is good.
> 
> I wonder if o->branch[12] need to be restored, though.  The only
> sensible thing the caller can do is to punt, but would it expect to
> be able to do some error reporting based on these fields, e.g.
> printf("merge of %s and %s failed", o->branch1, o->branch2) or
> something?  In addition to that kind of "state restoration", we may
> need to watch out for resource leaks, but I think there is none at
> least these three early returns.

I do not think that the caller can do anything sensible with *o after we
return an error: it means that we failed *somewhere* in that operation,
but does not say exactly where.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 7/9] merge-recursive: handle return values indicating errors
  2016-06-29 21:06   ` Junio C Hamano
@ 2016-07-01 11:08     ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 11:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > diff --git a/merge-recursive.c b/merge-recursive.c
> > index 6ab7dfc..bb075e3 100644
> > --- a/merge-recursive.c
> > +++ b/merge-recursive.c
> > @@ -266,8 +266,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
> >  		active_cache_tree = cache_tree();
> >  
> >  	if (!cache_tree_fully_valid(active_cache_tree) &&
> > -	    cache_tree_update(&the_index, 0) < 0)
> > -		die(_("error building trees"));
> > +	    cache_tree_update(&the_index, 0) < 0) {
> > +		error(_("error building trees"));
> > +		return NULL;
> > +	}
> 
> This actually is a BUG(), isn't it?  We have already verified that
> the cache is merged, so cache_tree_update() ought to be able to come
> up with the whole-tree hash.

I am not so sure. What if the disk is full?

> > @@ -548,19 +550,17 @@ static int update_stages(const char *path, const struct diff_filespec *o,
> >  	 */
> >  	int clear = 1;
> >  	int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
> > +	int ret = 0;
> > +
> >  	if (clear)
> > -		if (remove_file_from_cache(path))
> > -			return -1;
> > -	if (o)
> > -		if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
> > -			return -1;
> > -	if (a)
> > -		if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
> > -			return -1;
> > -	if (b)
> > -		if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
> > -			return -1;
> > -	return 0;
> > +		ret = remove_file_from_cache(path);
> > +	if (!ret && o)
> > +		ret = add_cacheinfo(o->mode, o->sha1, path, 1, 0, options);
> > +	if (!ret && a)
> > +		ret = add_cacheinfo(a->mode, a->sha1, path, 2, 0, options);
> > +	if (!ret && b)
> > +		ret = add_cacheinfo(b->mode, b->sha1, path, 3, 0, options);
> > +	return ret;
> >  }
> 
> Aren't the preimage and the postimage doing the same thing?  The
> only two differences I spot are (1) it is clear in the original that
> the returned value is -1 in the error case, even if the error
> convention of remove_file_from_cache() and add_cacheinfo() were "0
> is good, others are bad"; and (2) the control flow is easier to
> follow in the original.

Ah crap. This is a late left-over from the time when I thought I had to
introduce a "really fatal error" condition, i.e. return -128. I thought
that there might be errors that need to be handled differently, and
therefore it was essential that the return value -128 would not be
converted into a -1.

Will revert this hunk.

> > @@ -736,7 +736,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
> >  	return error(msg, path, _(": perhaps a D/F conflict?"));
> >  }
> >  
> > -static void update_file_flags(struct merge_options *o,
> > +static int update_file_flags(struct merge_options *o,
> >  			      const unsigned char *sha,
> >  			      unsigned mode,
> >  			      const char *path,
> > @@ -777,8 +777,7 @@ static void update_file_flags(struct merge_options *o,
> >  
> >  		if (make_room_for_path(o, path) < 0) {
> >  			update_wd = 0;
> > -			free(buf);
> > -			goto update_index;
> > +			goto free_buf;
> >  		}
> >  		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
> >  			int fd;
> > @@ -801,20 +800,22 @@ static void update_file_flags(struct merge_options *o,
> >  		} else
> >  			die(_("do not know what to do with %06o %s '%s'"),
> >  			    mode, sha1_to_hex(sha), path);
> > +free_buf:
> >  		free(buf);
> 
> I somehow find the above change harder to follow than the original.

Yeah, you noticed when commenting on the next patch why I did it. I added
a comment to the commit message.

> >  	}
> >   update_index:
> >  	if (update_cache)
> >  		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
> > +	return 0;
> >  }
> >  
> 
> This one is in line with the stated goal of the patch.
> 
> > @@ -1028,21 +1030,23 @@ static void handle_change_delete(struct merge_options *o,
> >  		 * correct; since there is no true "middle point" between
> >  		 * them, simply reuse the base version for virtual merge base.
> >  		 */
> > -		remove_file_from_cache(path);
> > -		update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
> > +		ret = remove_file_from_cache(path);
> > +		if (!ret)
> > +			ret = update_file(o, 0, o_sha, o_mode,
> > +					  renamed ? renamed : path);
> 
> As you noted in the log message, this does change the behaviour.  If
> remove returns non-zero for whatever reason, we still did update()
> in the original, but we no longer do.  Does this have negative
> effect to the overall codeflow?

Please note that remove_file_from_index() does not, in fact, anything but
0 for the moment. So the practical use of testing the return value is nil
with Git's current source code.

It is more about future-proofing than anything else.

As to this incantation specifically, if we ever fail in
remove_file_from_cache(), I would expect the code to fail fast. That is
how I implemented it in this patch.

> > @@ -1087,21 +1094,22 @@ static void conflict_rename_delete(struct merge_options *o,
> >  		b_mode = dest->mode;
> >  	}
> >  
> > -	handle_change_delete(o,
> > +	ret = handle_change_delete(o,
> >  			     o->call_depth ? orig->path : dest->path,
> >  			     orig->sha1, orig->mode,
> >  			     a_sha, a_mode,
> >  			     b_sha, b_mode,
> >  			     _("rename"), _("renamed"));
> > -
> > -	if (o->call_depth) {
> > -		remove_file_from_cache(dest->path);
> > -	} else {
> > -		update_stages(dest->path, NULL,
> > +	if (ret < 0)
> > +		return ret;
> > +	if (o->call_depth)
> > +		ret = remove_file_from_cache(dest->path);
> > +	else
> > +		ret = update_stages(dest->path, NULL,
> >  			      rename_branch == o->branch1 ? dest : NULL,
> >  			      rename_branch == o->branch1 ? NULL : dest);
> > -	}
> 
> Similarly, if handle_change_delete() returns non-zero, we no longer
> call remove() or update().  Is that a good behaviour change?

Please note that handle_change_delete() did not return any value before
this patch series. In those cases that now return -1, it previously
die()d.

My patch ensures that the latter functions are not called in the error
case. Just like before.

> > -static void handle_file(struct merge_options *o,
> > +static int handle_file(struct merge_options *o,
> >  			struct diff_filespec *rename,
> >  			int stage,
> >  			struct rename_conflict_info *ci)
> 
> Likewise.

Ditto.

> > -static void conflict_rename_rename_1to2(struct merge_options *o,
> > +static int conflict_rename_rename_1to2(struct merge_options *o,
> >  					struct rename_conflict_info *ci)
> >  {
> > ...
> > -		if (merge_file_one(o, one->path,
> > +		if ((ret = merge_file_one(o, one->path,
> >  				 one->sha1, one->mode,
> >  				 a->sha1, a->mode,
> >  				 b->sha1, b->mode,
> > -				 ci->branch1, ci->branch2, &mfi) < 0)
> > -			return;
> > +				 ci->branch1, ci->branch2, &mfi)))
> > +			return ret;
> 
> This does not change behaviour.
> 
> > @@ -1194,7 +1208,8 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
> >  		 * pathname and then either rename the add-source file to that
> >  		 * unique path, or use that unique path instead of src here.
> >  		 */
> > -		update_file(o, 0, mfi.sha, mfi.mode, one->path);
> > +		if ((ret = update_file(o, 0, mfi.sha, mfi.mode, one->path)))
> > +			return ret;
> 
> But this does.

As stated above: it does not. In case of an error in update_file() (which
is now reported via a return value, in contrast to the previous behavior),
just like before, we skip the rest of the function.

The difference is that we now clean up our resources and return an error
instead of die()ing.

> > @@ -1205,22 +1220,31 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
> >  		 * resolving the conflict at that path in its favor.
> >  		 */
> >  		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
> > -		if (add)
> > -			update_file(o, 0, add->sha1, add->mode, a->path);
> > +		if (add) {
> > +			if ((ret = update_file(o, 0, add->sha1, add->mode,
> > +					a->path)))
> > +				return ret;
> > +		}
> 
> So does this.

Same as above (i.e. same behavior as previously).

> >  		else
> >  			remove_file_from_cache(a->path);
> >  		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
> > -		if (add)
> > -			update_file(o, 0, add->sha1, add->mode, b->path);
> > +		if (add) {
> > +			if ((ret = update_file(o, 0, add->sha1, add->mode,
> > +					b->path)))
> > +				return ret;
> > +		}
> 
> And this.

Yep. Same behavior.

> >  	} else {
> > -		handle_file(o, a, 2, ci);
> > -		handle_file(o, b, 3, ci);
> > +		if ((ret = handle_file(o, a, 2, ci)) ||
> > +		    (ret = handle_file(o, b, 3, ci)))
> > +			return ret;
> 
> And this.

Yep. Same behavior.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 8/9] merge-recursive: switch to returning errors instead of dying
  2016-06-29 21:19   ` Junio C Hamano
@ 2016-07-01 11:14     ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 11:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > @@ -743,6 +741,8 @@ static int update_file_flags(struct merge_options *o,
> >  			      int update_cache,
> >  			      int update_wd)
> > ...
> > +			ret = error_errno(_("do not know what to do with %06o %s '%s'"),
> > +				mode, sha1_to_hex(sha), path);
> >  free_buf:
> 
> OK, with a few more users of this label, it no longer looks so
> strange to me to have this label here.
> 
> At least, match the indentation level to the existing one we see
> below, though?

Okay, fixed.

Although I have to say that I do not quite understand the meaning of the
one-space indentation, in particular since the later "error_return" label
lacks it.

> >  		free(buf);
> >  	}
> >   update_index:
> > [...]
> > @@ -1861,7 +1867,7 @@ static int process_entry(struct merge_options *o,
> >  		 */
> >  		remove_file(o, 1, path, !a_mode);
> >  	} else
> > -		die(_("Fatal merge failure, shouldn't happen."));
> > +		return error(_("Fatal merge failure, shouldn't happen."));
> 
> Isn't this BUG()?

I guess it is! Fixed.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 9/9] am: make a direct call to merge_recursive
  2016-06-29 21:23   ` Junio C Hamano
@ 2016-07-01 12:46     ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 12:46 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > -	cp.git_cmd = 1;
> > +	init_merge_options(&o);
> > +
> > +	o.branch1 = "HEAD";
> > +	his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
> > +	o.branch2 = his_tree_name;
> >  
> > -	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
> > -			 sha1_to_hex(his_tree), linelen(state->msg), state->msg);
> >  	if (state->quiet)
> > -		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
> > +		o.verbosity = 0;
> >  
> > -	argv_array_push(&cp.args, "merge-recursive");
> > -	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
> > -	argv_array_push(&cp.args, "--");
> > -	argv_array_push(&cp.args, sha1_to_hex(our_tree));
> > -	argv_array_push(&cp.args, sha1_to_hex(his_tree));
> > +	status = merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result);
> > +	if (status < 0)
> > +		exit(128);
> > +	free(his_tree_name);
> >  
> > -	status = run_command(&cp) ? (-1) : 0;
> > -	discard_cache();
> > -	read_cache();
> >  	return status;
> >  }
> 
> Is this a correct conversion?
> 
> We used to prepare the command line and called run_command() and
> without dying returned status with the error status from the
> merge-recursive that was spawned by run_command().
> 
> The new code does not report failure to the caller and instead dies.

True, this is incorrect.

I took a step back and realized that the most appropriate course of
action would be to revert the commit that calls run_command() to begin
with. This also solves the authorship issue.

And while at it, I also noticed another sore to my eye that I had noticed
repeatedly and now fix as part of this patch series.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 5/9] merge-recursive: avoid returning a wholesale struct
  2016-06-29 20:21   ` Junio C Hamano
@ 2016-07-01 13:48     ` Johannes Schindelin
  2016-07-01 15:02       ` Eric Wong
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 13:48 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 29 Jun 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > It is technically allowed, as per C89, for functions' return type to
> > be complete structs (i.e. *not* just pointers to structs), but it is
> > bad practice.
> 
> Not necessarily.

Okay, I fixed it.

> > This is a very late attempt to contain the damage done by this developer
> > in 6d297f8 (Status update on merge-recursive in C, 2006-07-08) which
> > introduced such a return type.
> >
> > It will also help the current effort to libify merge-recursive.c, as
> > it will allow us to return proper error codes later.
> 
> But this part of the motivation does make sense.  Having to pass an
> extra int &error_code field, only because the return value is
> already used for something else, is a lot more weird.

While I still think it is inelegant to return a whole struct
(understanding the machine code aspects of it), I toned it down to say
that we just do not use that construct in Git's source code. And I kept
the paragraph that you found more convincing, of course.

Ciao,
Dscho

P.S.: If it is not too much of a problem, may I ask you to simply delete
remainders of my patches when replying and not commenting on them? I just
deleted 226 lines after verifying that you really did not respond to any
part of it in the unhelpful Alpine client (which I still use because it
*still* fits much better with my workflow than anything else, by a lot).
Again, not a big deal if it would make your life more painful.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-30  9:23       ` Jeff King
@ 2016-07-01 13:51         ` Johannes Schindelin
  2016-07-01 18:16           ` Jeff King
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-01 13:51 UTC (permalink / raw)
  To: Jeff King; +Cc: Junio C Hamano, git

Hi Peff,

On Thu, 30 Jun 2016, Jeff King wrote:

> On Thu, Jun 30, 2016 at 10:42:37AM +0200, Johannes Schindelin wrote:
> 
> > > > The vast majority of error messages in Git's source code which report a
> > > > bug use the convention to prefix the message with "BUG:".
> > > 
> > > Good thing to do.
> > > 
> > > But if we were to review and apply a 200+ line patch, I wonder if we
> > > want to go one step further to allow us to write
> > > 
> > >     BUG("killed-file %s not found", name);
> > > 
> > > instead.
> > 
> > If the idea is to make it easier to find, I would wager a guess that
> > 'die("BUG:' would be just as good a search term. Even better, I think,
> > because 'BUG' would also match comments.
> 
> I have been tempted to switch to BUG(), because it would make it easy to
> call abort() and get a coredump (and therefore a stack trace).

Please keep in mind that abort() does not produce stackdumps with MinGW.
So at least Windows developers would not be better off.

> On the other hand:
> 
>   - we could always trigger such behavior in die() by looking for "BUG:" in
>     the output string. :)
> 
>   - it's also sometimes useful to get a stack trace from a regular
>     non-bug die(). So maybe something optional like:
> 
>       if (git_env_bool("GIT_ABORT_ON_DIE", 0))
>               abort();
> 
>     would be helpful (since you have to turn it on ahead of time, you
>     could also just run the program under gdb, of course; however, I
>     sometimes find that it's hard to get gdb where you want it because
>     git spawns so many sub-programs. Or maybe I just need to get better
>     at using gdb's child-following options).

Heh. I still find myself using that good old trick where I set a variable,
loop while it is set and print out the pid, waiting for a debugger to
attach and re-set that variable.

> The other thing BUG() would get us is that we could turn it into a macro
> (on systems with vararg macros) and report things like __FILE__ and
> __LINE__.  In practice, though our BUG messages are unique enough that
> there is no problem finding the source.

That would be very nice *also* for error() messages. But I guess we cannot
have it, vararg macros being a feature rather than a standard.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 5/9] merge-recursive: avoid returning a wholesale struct
  2016-07-01 13:48     ` Johannes Schindelin
@ 2016-07-01 15:02       ` Eric Wong
  0 siblings, 0 replies; 262+ messages in thread
From: Eric Wong @ 2016-07-01 15:02 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> P.S.: If it is not too much of a problem, may I ask you to simply delete
> remainders of my patches when replying and not commenting on them? I just
> deleted 226 lines after verifying that you really did not respond to any
> part of it in the unhelpful Alpine client (which I still use because it
> *still* fits much better with my workflow than anything else, by a lot).
> Again, not a big deal if it would make your life more painful.

I find the over-quoting annoying, too.  Fwiw, my muttrc has:

bind pager , skip-quoted

Which allows me to hit the comma key to skip over quoted text.
Not sure if Alpine or other clients have something similar.
But I'd rather not waste the keystroke/bandwidth/storage.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-07-01  9:23     ` Johannes Schindelin
@ 2016-07-01 15:31       ` Junio C Hamano
  2016-07-02  7:20         ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-01 15:31 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> I agree that the comment is not very good currently. But I disagree that
> we are better off without any comment here.

I meant we are better off without your particular version of comment
which is misleading.  I am all for a better comment to help those
who are new to the codepath.

> I would like to propose this diff instead (it is larger, but with a net
> savings of one line):
>
> -- snipsnap --
> diff --git a/merge-recursive.c b/merge-recursive.c
> index d5a593c..0eda51a 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -658,24 +658,22 @@ static int was_tracked(const char *path)
>  {
>  	int pos = cache_name_pos(path, strlen(path));
>  
> -	if (pos < 0)
> -		pos = -1 - pos;
> -	while (pos < active_nr &&
> -	       !strcmp(path, active_cache[pos]->name)) {
> +	if (pos >= 0)
> +		return pos < active_nr;
> +	/*
> +	 * cache_name_pos() looks for stage == 0, even if we did not ask for
> +	 * it. Let's look for stage == 2 now.
> +	 */

I think this keeps the same phrasing from the original that makes
the comment misleading.  It "looks for stage == 0" is not the whole
story but only half.  It looks for a place to insert the path at
stage #0" is.  Your half is used by the "if (0 <= pos)" you split
out into a separate statement above already, and the untold half is
needed to explain why this loop is correct.

It returns the place to insert stage #0 entry, so if you are looking
for stage #1 or higher, you only have to loop while the path
matches, because the entries are sorted by <path, stage>.

And with that understanding, there is no strong reason to special
case "ah, we found stage #0 entry" at all.

> +	for (pos = -1 - pos; pos < active_nr &&
> +	     !strcmp(path, active_cache[pos]->name); pos++)
>  		/*
>  		 * If stage #0, it is definitely tracked.
>  		 * If it has stage #2 then it was tracked
>  		 * before this merge started.  All other
>  		 * cases the path was not tracked.
>  		 */
> -		switch (ce_stage(active_cache[pos])) {
> -		case 0:
> -		case 2:
> +		if (ce_stage(active_cache[pos]) == 2)
>  			return 1;
> -		}
> -		pos++;
> -	}
>  	return 0;
>  }
>  

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 3/9] Prepare the builtins for a libified merge_recursive()
  2016-07-01 10:14     ` Johannes Schindelin
@ 2016-07-01 15:43       ` Junio C Hamano
  2016-07-02  7:24         ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-01 15:43 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> > A truly libified function does not die() just for fun.
>> 
>> The sentence is wasting bits.  After all, a helper function in
>> run-once-and-exit program does not die() just for fun, either.
>
> This sentence does not so much target *you* personally as audience, but
> the occasional reader of the log who wonders: "Why don't we just call
> die()? We would not have to worry about passing back the return value
> through all those long call chains..."

I was (and I am still) reacting mostly to "just for fun".

> Even more natural is it to guess that the code will call error(), just
> like we do almost everywhere else.
> ...
>> But that does not mesh very well with the stated objective of the
>> patch.
> ...
> I could imagine that you wanted even more fine-grained control, where we
> have a range of return values indicating different error conditions.

I personally don't.  I was pointing out the discrepancy between what
the introduction says, i.e. "this way is way more flexible for the
callers when they want to do their own error handling", and what the
code actually does.  If the explanation said "This series does not
give the full flexibility potential callers may desire yet, but at
least gives enough flexibility to do 'I do not want the called
function to die, but append my own error message before I die
myself'.", that is certainly an understandable stance to take, I
would say.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 4/9] merge_recursive: abort properly upon errors
  2016-07-01 10:16     ` Johannes Schindelin
@ 2016-07-01 15:56       ` Junio C Hamano
  2016-07-02 11:30         ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-01 15:56 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> >  		saved_b2 = o->branch2;
>> >  		o->branch1 = "Temporary merge branch 1";
>> >  		o->branch2 = "Temporary merge branch 2";
>> > -		merge_recursive(o, merged_common_ancestors, iter->item,
>> > -				NULL, &merged_common_ancestors);
>> > +		if (merge_recursive(o, merged_common_ancestors, iter->item,
>> > +				NULL, &merged_common_ancestors) < 0)
>> > +			return -1;
>> >  		o->branch1 = saved_b1;
>> >  		o->branch2 = saved_b2;
>> >  		o->call_depth--;
>> 
>> I wonder if o->branch[12] need to be restored, though.  The only
>> sensible thing the caller can do is to punt,...
>
> I do not think that the caller can do anything sensible with *o after we
> return an error...

That is totally up to what this patch does, isn't it?

By deliberately keeping o->branch[12] to point at the temporary
names and not restoring, this patch declares "the caller cannot do
anything sensible with *o".  If it restores, the caller still can.
Even with this step as-is, the caller can tell at which recursion
level the merge failed by looking at o->call_depth, for example.

I do not think the current set of callers, and a new one you will be
introducing, would prefer to be able to do something with *o after
the failure return from this function, so in that sense, I do not
care deeply either way.

But if the patch is making a policy decision "*o is undefined upon
error return from this function", it would help people who want to
build on top of this codebase to add that to the comment before the
function, wouldn't it?


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 9/9] am: make a direct call to merge_recursive
  2016-06-30  8:38     ` Johannes Schindelin
@ 2016-07-01 16:03       ` Junio C Hamano
  0 siblings, 0 replies; 262+ messages in thread
From: Junio C Hamano @ 2016-07-01 16:03 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> On Wed, 29 Jun 2016, Junio C Hamano wrote:
>
>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
>> 
>> > From: Junio C Hamano <gitster@pobox.com>
>> 
>> Did I write this thing?
>
> Yes, you did. It was db05d6194d3f9ea9e64163944961d5f6e85302be as part of
> pu@{2016-06-15}.

Ah, OK, that is quite old, dating from Oct 2015.  Thanks for a
pointer.


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-07-01 13:51         ` Johannes Schindelin
@ 2016-07-01 18:16           ` Jeff King
  0 siblings, 0 replies; 262+ messages in thread
From: Jeff King @ 2016-07-01 18:16 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, git

On Fri, Jul 01, 2016 at 03:51:33PM +0200, Johannes Schindelin wrote:

> > > If the idea is to make it easier to find, I would wager a guess that
> > > 'die("BUG:' would be just as good a search term. Even better, I think,
> > > because 'BUG' would also match comments.
> > 
> > I have been tempted to switch to BUG(), because it would make it easy to
> > call abort() and get a coredump (and therefore a stack trace).
> 
> Please keep in mind that abort() does not produce stackdumps with MinGW.
> So at least Windows developers would not be better off.

It doesn't in Linux either. But you can use the core file to get a
backtrace. Do you not get cores at all in Windows? Yuck. :)

> Heh. I still find myself using that good old trick where I set a variable,
> loop while it is set and print out the pid, waiting for a debugger to
> attach and re-set that variable.

That's a new one to me. Gross. :)

Usually I can coax git into running "gdbserver ... git foo" instead of
git foo" via config. But sometimes the values are hard-coded (for
instance, the way we call pack-objects, which I recently made a bit more
flexible).

> > The other thing BUG() would get us is that we could turn it into a macro
> > (on systems with vararg macros) and report things like __FILE__ and
> > __LINE__.  In practice, though our BUG messages are unique enough that
> > there is no problem finding the source.
> 
> That would be very nice *also* for error() messages. But I guess we cannot
> have it, vararg macros being a feature rather than a standard.

It is a standard, it's just C99, which we've been rather slow to
embrace.

However we have prior art in the trace code. If your system supports
variadic macros, then we define the macros to pass the file/line info.
Otherwise, we skip the macros, and those names point directly to
functions.

So it would not be too hard to implement. I've never bothered, though,
because in my experience the interesting thing is almost never "on what
line was error() called", but rather "what line called the function that
called error()". I.e., the stack trace.

I looked a while ago at trying to auto-generate stack traces in git
using something like GNU's backtrace (obviously not portable, but it's a
start). My conclusion was that the simplest thing is to just generate a
core and run "gdb -ex bt" on it.

-Peff

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-06-29 11:36 ` [PATCH 1/9] Report bugs consistently Johannes Schindelin
                     ` (3 preceding siblings ...)
  2016-06-30  5:38   ` Johannes Sixt
@ 2016-07-02  5:11   ` Duy Nguyen
  2016-07-02  7:25     ` Johannes Schindelin
  4 siblings, 1 reply; 262+ messages in thread
From: Duy Nguyen @ 2016-07-02  5:11 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Git Mailing List, Junio C Hamano

On Wed, Jun 29, 2016 at 1:36 PM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> @@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
>
>                         if (!sha_eq(a->sha1, b->sha1))
>                                 result.clean = 0;
> -               } else {
> -                       die(_("unsupported object type in the tree"));
> -               }
> +               } else
> +                       die(_("BUG: unsupported object type in the tree"));

As a message targeting developers, we do not need to mark this for
translation. There are a couple other _() in this patch that should be
removed as well.
-- 
Duy

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-07-01 15:31       ` Junio C Hamano
@ 2016-07-02  7:20         ` Johannes Schindelin
  2016-07-06 15:30           ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-02  7:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Fri, 1 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > I would like to propose this diff instead (it is larger, but with a net
> > savings of one line):
> >
> > -- snipsnap --
> > diff --git a/merge-recursive.c b/merge-recursive.c
> > index d5a593c..0eda51a 100644
> > --- a/merge-recursive.c
> > +++ b/merge-recursive.c
> > @@ -658,24 +658,22 @@ static int was_tracked(const char *path)
> >  {
> >  	int pos = cache_name_pos(path, strlen(path));
> >  
> > -	if (pos < 0)
> > -		pos = -1 - pos;
> > -	while (pos < active_nr &&
> > -	       !strcmp(path, active_cache[pos]->name)) {
> > +	if (pos >= 0)
> > +		return pos < active_nr;
> > +	/*
> > +	 * cache_name_pos() looks for stage == 0, even if we did not ask for
> > +	 * it. Let's look for stage == 2 now.
> > +	 */
> 
> I think this keeps the same phrasing from the original that makes
> the comment misleading.  It "looks for stage == 0" is not the whole
> story but only half.

Yes, it is the relevant part of the story to explain why we're not done
when pos < 0.

To understand why we're not done yet, the crucial point is *not* that the
return value encodes the insert position. The crucial point is that
despite asking for an index entry matching a specific name, we might not
find one, *even if there is one*. And the reason is that cache_name_pos()
does not quite do what the name suggests.

I am sorry to disagree with you here: I really find it important to
document this potential misunderstanding.

> It looks for a place to insert the path at stage #0" is.  Your half is
> used by the "if (0 <= pos)" you split out into a separate statement
> above already, and the untold half is needed to explain why this loop is
> correct.

True. I did not bother to document that part. Because even if I was
puzzled by the logic handling a negative return value, the assignment "pos
= -1 - pos" made it very clear to me what was happening. I am not *that*
easily puzzled.

> It returns the place to insert stage #0 entry, so if you are looking
> for stage #1 or higher, you only have to loop while the path
> matches, because the entries are sorted by <path, stage>.
> 
> And with that understanding, there is no strong reason to special
> case "ah, we found stage #0 entry" at all.

There is one, and I mentioned it. In the common case (i.e. we found an
entry right away because it is in stage 0), the loop unnecessarily
compares the name *again*: index_name_pos() already performed that
comparison and if it returned a non-negative value, we know that that
comparison was successful.

I find the combination of clarification, code reduction, and separation
between conflated cases (stage 0 does not need that loop at all)
compelling enough to state that my patch is an improvement overall. We can
discuss about wording and what details to mention in the comments, of
course.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 3/9] Prepare the builtins for a libified merge_recursive()
  2016-07-01 15:43       ` Junio C Hamano
@ 2016-07-02  7:24         ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-02  7:24 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Fri, 1 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> > A truly libified function does not die() just for fun.
> >> 
> >> The sentence is wasting bits.  After all, a helper function in
> >> run-once-and-exit program does not die() just for fun, either.
> >
> > This sentence does not so much target *you* personally as audience, but
> > the occasional reader of the log who wonders: "Why don't we just call
> > die()? We would not have to worry about passing back the return value
> > through all those long call chains..."
> 
> I was (and I am still) reacting mostly to "just for fun".

Yeah, sorry, that part was lost on me.

> > Even more natural is it to guess that the code will call error(), just
> > like we do almost everywhere else.
> > ...
> >> But that does not mesh very well with the stated objective of the
> >> patch.
> > ...
> > I could imagine that you wanted even more fine-grained control, where we
> > have a range of return values indicating different error conditions.
> 
> I personally don't.  I was pointing out the discrepancy between what
> the introduction says, i.e. "this way is way more flexible for the
> callers when they want to do their own error handling", and what the
> code actually does.  If the explanation said "This series does not
> give the full flexibility potential callers may desire yet, but at
> least gives enough flexibility to do 'I do not want the called
> function to die, but append my own error message before I die
> myself'.", that is certainly an understandable stance to take, I
> would say.

Ah, but the message did not say "error message handling", but "error
handling". With my limited command of the English language, I tried to
convey that this patch allows the callers to do something when the called
operation reported an error. Previously they did not get the chance.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-07-02  5:11   ` Duy Nguyen
@ 2016-07-02  7:25     ` Johannes Schindelin
  2016-07-02  8:01       ` Duy Nguyen
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-02  7:25 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List, Junio C Hamano

Hi Duy,

On Sat, 2 Jul 2016, Duy Nguyen wrote:

> On Wed, Jun 29, 2016 at 1:36 PM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > @@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
> >
> >                         if (!sha_eq(a->sha1, b->sha1))
> >                                 result.clean = 0;
> > -               } else {
> > -                       die(_("unsupported object type in the tree"));
> > -               }
> > +               } else
> > +                       die(_("BUG: unsupported object type in the tree"));
> 
> As a message targeting developers, we do not need to mark this for
> translation. There are a couple other _() in this patch that should be
> removed as well.

Yes, Hannes already pointed that out.

My answer is the same: it is not the purpose of this patch series to fix
this, and therefore it retains the previous behavior.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-07-02  7:25     ` Johannes Schindelin
@ 2016-07-02  8:01       ` Duy Nguyen
  2016-07-05 11:32         ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Duy Nguyen @ 2016-07-02  8:01 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Git Mailing List, Junio C Hamano

On Sat, Jul 2, 2016 at 9:25 AM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> Hi Duy,
>
> On Sat, 2 Jul 2016, Duy Nguyen wrote:
>
>> On Wed, Jun 29, 2016 at 1:36 PM, Johannes Schindelin
>> <johannes.schindelin@gmx.de> wrote:
>> > @@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
>> >
>> >                         if (!sha_eq(a->sha1, b->sha1))
>> >                                 result.clean = 0;
>> > -               } else {
>> > -                       die(_("unsupported object type in the tree"));
>> > -               }
>> > +               } else
>> > +                       die(_("BUG: unsupported object type in the tree"));
>>
>> As a message targeting developers, we do not need to mark this for
>> translation. There are a couple other _() in this patch that should be
>> removed as well.
>
> Yes, Hannes already pointed that out.

Ah.. sorry I didn't read the whole thread.

> My answer is the same: it is not the purpose of this patch series to fix
> this, and therefore it retains the previous behavior.

You're changing the string and adding more work to translators. So
either leave the string untouched, or drop _().
-- 
Duy

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 4/9] merge_recursive: abort properly upon errors
  2016-07-01 15:56       ` Junio C Hamano
@ 2016-07-02 11:30         ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-02 11:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Fri, 1 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> >  		saved_b2 = o->branch2;
> >> >  		o->branch1 = "Temporary merge branch 1";
> >> >  		o->branch2 = "Temporary merge branch 2";
> >> > -		merge_recursive(o, merged_common_ancestors, iter->item,
> >> > -				NULL, &merged_common_ancestors);
> >> > +		if (merge_recursive(o, merged_common_ancestors, iter->item,
> >> > +				NULL, &merged_common_ancestors) < 0)
> >> > +			return -1;
> >> >  		o->branch1 = saved_b1;
> >> >  		o->branch2 = saved_b2;
> >> >  		o->call_depth--;
> >> 
> >> I wonder if o->branch[12] need to be restored, though.  The only
> >> sensible thing the caller can do is to punt,...
> >
> > I do not think that the caller can do anything sensible with *o after we
> > return an error...
> 
> That is totally up to what this patch does, isn't it?

No, not really. We do not really know *where* in the recursive merge the
failure happened.

All we know is that it happened while trying to merge the temporary
branches.

> By deliberately keeping o->branch[12] to point at the temporary
> names and not restoring, this patch declares "the caller cannot do
> anything sensible with *o".  If it restores, the caller still can.

But would restoring the branch names not give the false impression that
the error occurred during a different phase of the recursive merge?

> Even with this step as-is, the caller can tell at which recursion
> level the merge failed by looking at o->call_depth, for example.

Well, not really:

1) please note the o->call_depth-- that is *also* skipped in case of an
   error after my patch, and

2) what use is the recursion level if an arbitrary number of merges could
   happen at the same recursion depth?

In short, I do not think that resetting those values in *o could bring any
benefit to the caller, short of introducing those fine-grained error
values I mentioned elsewhere.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v2 00/17] Use merge_recursive() directly in the builtin am
  2016-06-29 11:36 [PATCH 0/9] Use merge_recursive() directly in the builtin am Johannes Schindelin
                   ` (8 preceding siblings ...)
  2016-06-29 11:38 ` [PATCH 9/9] am: make a direct call to merge_recursive Johannes Schindelin
@ 2016-07-05 11:22 ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 01/17] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
                     ` (18 more replies)
  9 siblings, 19 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:22 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

This is the second iteration of the long-awaited re-roll of the attempt to
avoid spawning merge-recursive from the builtin am and use merge_recursive()
directly instead.

The *real* reason for the reroll is that I need a libified recursive
merge to accelerate the interactive rebase by teaching the sequencer to
do rebase -i's grunt work.

In this endeavor, we need to be extra careful to retain backwards
compatibility. The test script t6022-merge-rename.sh, for example, verifies
that `git pull` exits with status 128 in case of a fatal error. To that end,
we need to make sure that fatal errors are handled by existing (builtin)
users via exit(128) (or die(), which calls exit(128) at the end).  New users
(such as a builtin helper doing rebase -i's grunt work) may want to print
some helpful advice what happened and how to get out of this mess before
erroring out.

The changes relative to the first iteration of this patch series:

- a variable that could be used uninitialized is now initialized (thanks,
  Travis & clang!)

- several commit messages were touched up (and I hope y'all agree, improved)

- an unnecessary hunk was reverted (this was a left-over from an
  unpublished iteration that needed to retain return values faithfully, i.e.
  it made a difference between -1 and -128 as error value)

- Junio's patch to use recursive_merge() directly in the builtin am was
  replaced by a different solution

- an error message was identified as, and converted into, a bug report
  instead

- the code in was_tracked() now avoids a loop when it is unnecessary,
  and further clarifies why we keep looking when cache_name_pos() did
  not find the entry we asked for

- die("BUG: ...") statements are no longer translated

- one die("BUG: ...") report that continued in upper-case after the "BUG:"
  prefix was fixed

- I addressed a gender bias that has been bugging me ever since I noticed it

- recursive merge's error messages are now printed after flushing the
  output buffer (instead of swallowing that output)

- callers of the recursive merge can now ask that the output buffer not be
  flushed, but retained without printing it instead. This gives the caller the
  control about handling errors which Junio asked for.

- some long-standing bugs have been recognized and addressed:

  - when the recursive merge failed, it lost the buffered output

  - the output buffer of the recursive merge was never released

  - some stdout/stderr interference that we tried to address in 2007 is
    now fully addressed (the progress output could be printed in the
    middle of the commit title because the latter was still directly printed
    to stdout, which is buffered, instead of being buffered and flushed)

- a lot of unnecessary 'ret' variables are gone now: originally, I wanted to
  retain the *exact* return value, but now errors are indicated by -1,
  always

- lastly, I remembered that my original attempt at fixing the pull --rebase
  issue contained a test case, and I forward-ported that, and augmented it

So while I addressed all comments, I also went through the patch series a
couple of times myself and whatever bugged me, I tried to resolve, too.

This patch series touches rather important code. Now that I addressed
concerns such as fixing translated bug reports, I would appreciate thorough
reviews with a focus on the critical parts of the code, those that could
result in regressions.


Johannes Schindelin (17):
  Verify that `git pull --rebase` shows the helpful advice when failing
  Report bugs consistently
  Avoid translating bug messages
  merge-recursive: clarify code in was_tracked()
  Prepare the builtins for a libified merge_recursive()
  merge_recursive: abort properly upon errors
  merge-recursive: avoid returning a wholesale struct
  merge-recursive: allow write_tree_from_memory() to error out
  merge-recursive: handle return values indicating errors
  merge-recursive: switch to returning errors instead of dying
  am: counteract gender bias
  am -3: use merge_recursive() directly again
  merge-recursive: flush output buffer before printing error messages
  merge-recursive: write the commit title in one go
  merge-recursive: offer an option to retain the output in 'obuf'
  Ensure that the output buffer is released after calling merge_trees()
  merge-recursive: flush output buffer even when erroring out

 builtin/am.c           |  55 ++----
 builtin/checkout.c     |   5 +-
 builtin/ls-files.c     |   3 +-
 builtin/merge.c        |   2 +
 builtin/update-index.c |   2 +-
 grep.c                 |   8 +-
 imap-send.c            |   4 +-
 merge-recursive.c      | 490 +++++++++++++++++++++++++++++--------------------
 merge-recursive.h      |   2 +-
 sequencer.c            |   5 +
 sha1_file.c            |   4 +-
 t/t5520-pull.sh        |  30 +++
 trailer.c              |   2 +-
 transport.c            |   2 +-
 wt-status.c            |   4 +-
 15 files changed, 369 insertions(+), 249 deletions(-)

Published-As: https://github.com/dscho/git/releases/tag/am-3-merge-recursive-direct-v2
Interdiff vs v1:

 diff --git a/builtin/am.c b/builtin/am.c
 index dd41154..be652f9 100644
 --- a/builtin/am.c
 +++ b/builtin/am.c
 @@ -1578,45 +1578,16 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
  }
  
  /**
 - * Do the three-way merge using fake ancestor, his tree constructed
 - * from the fake ancestor and the postimage of the patch, and our
 - * state.
 - */
 -static int run_fallback_merge_recursive(const struct am_state *state,
 -					unsigned char *orig_tree,
 -					unsigned char *our_tree,
 -					unsigned char *his_tree)
 -{
 -	const unsigned char *bases[1] = {orig_tree};
 -	struct merge_options o;
 -	struct commit *result;
 -	char *his_tree_name;
 -	int status;
 -
 -	init_merge_options(&o);
 -
 -	o.branch1 = "HEAD";
 -	his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 -	o.branch2 = his_tree_name;
 -
 -	if (state->quiet)
 -		o.verbosity = 0;
 -
 -	status = merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result);
 -	if (status < 0)
 -		exit(128);
 -	free(his_tree_name);
 -
 -	return status;
 -}
 -
 -/**
   * Attempt a threeway merge, using index_path as the temporary index.
   */
  static int fall_back_threeway(const struct am_state *state, const char *index_path)
  {
 -	unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
 +	unsigned char orig_tree[GIT_SHA1_RAWSZ], her_tree[GIT_SHA1_RAWSZ],
  		      our_tree[GIT_SHA1_RAWSZ];
 +	const unsigned char *bases[1] = {orig_tree};
 +	struct merge_options o;
 +	struct commit *result;
 +	char *her_tree_name;
  
  	if (get_sha1("HEAD", our_tree) < 0)
  		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
 @@ -1652,7 +1623,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  		return error(_("Did you hand edit your patch?\n"
  				"It does not apply to blobs recorded in its index."));
  
 -	if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
 +	if (write_index_as_tree(her_tree, &the_index, index_path, 0, NULL))
  		return error("could not write tree");
  
  	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
 @@ -1662,17 +1633,28 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  
  	/*
  	 * This is not so wrong. Depending on which base we picked, orig_tree
 -	 * may be wildly different from ours, but his_tree has the same set of
 +	 * may be wildly different from ours, but her_tree has the same set of
  	 * wildly different changes in parts the patch did not touch, so
  	 * recursive ends up canceling them, saying that we reverted all those
  	 * changes.
  	 */
  
 -	if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) {
 +	init_merge_options(&o);
 +
 +	o.branch1 = "HEAD";
 +	her_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 +	o.branch2 = her_tree_name;
 +
 +	if (state->quiet)
 +		o.verbosity = 0;
 +
 +	if (merge_recursive_generic(&o, our_tree, her_tree, 1, bases, &result)) {
  		rerere(state->allow_rerere_autoupdate);
 +		free(her_tree_name);
  		return error(_("Failed to merge in the changes."));
  	}
  
 +	free(her_tree_name);
  	return 0;
  }
  
 diff --git a/builtin/checkout.c b/builtin/checkout.c
 index 14312f7..ced4ac4 100644
 --- a/builtin/checkout.c
 +++ b/builtin/checkout.c
 @@ -573,6 +573,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
  				exit(128);
  			ret = reset_tree(new->commit->tree, opts, 0,
  					 writeout_error);
 +			strbuf_release(&o.obuf);
  			if (ret)
  				return ret;
  		}
 diff --git a/builtin/merge.c b/builtin/merge.c
 index 133b853..7b898db 100644
 --- a/builtin/merge.c
 +++ b/builtin/merge.c
 @@ -1552,8 +1552,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
  		ret = try_merge_strategy(use_strategies[i]->name,
  					 common, remoteheads,
  					 head_commit, head_arg);
 -		if (ret < 0)
 -			exit(128);
  		if (!option_commit && !ret) {
  			merge_was_ok = 1;
  			/*
 diff --git a/imap-send.c b/imap-send.c
 index cd39805..369f72a 100644
 --- a/imap-send.c
 +++ b/imap-send.c
 @@ -506,7 +506,7 @@ static char *next_arg(char **s)
  
  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
  {
 -	int ret;
 +	int ret = -1;
  	va_list va;
  
  	va_start(va, fmt);
 diff --git a/merge-recursive.c b/merge-recursive.c
 index d5a593c..d94f853 100644
 --- a/merge-recursive.c
 +++ b/merge-recursive.c
 @@ -23,6 +23,37 @@
  #include "dir.h"
  #include "submodule.h"
  
 +static void flush_output(struct merge_options *o)
 +{
 +	if (o->buffer_output < 2 && o->obuf.len) {
 +		fputs(o->obuf.buf, stdout);
 +		strbuf_reset(&o->obuf);
 +	}
 +}
 +
 +static int err(struct merge_options *o, const char *err, ...)
 +{
 +	va_list params;
 +
 +	va_start(params, err);
 +	if (o->buffer_output < 2)
 +		flush_output(o);
 +	else {
 +		strbuf_complete(&o->obuf, '\n');
 +		strbuf_addstr(&o->obuf, "error: ");
 +	}
 +	strbuf_vaddf(&o->obuf, err, params);
 +	if (o->buffer_output > 1)
 +		strbuf_addch(&o->obuf, '\n');
 +	else {
 +		error("%s", o->obuf.buf);
 +		strbuf_reset(&o->obuf);
 +	}
 +	va_end(params);
 +
 +	return -1;
 +}
 +
  static struct tree *shift_tree_object(struct tree *one, struct tree *two,
  				      const char *subtree_shift)
  {
 @@ -148,14 +179,6 @@ static int show(struct merge_options *o, int v)
  	return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
  }
  
 -static void flush_output(struct merge_options *o)
 -{
 -	if (o->obuf.len) {
 -		fputs(o->obuf.buf, stdout);
 -		strbuf_reset(&o->obuf);
 -	}
 -}
 -
  __attribute__((format (printf, 3, 4)))
  static void output(struct merge_options *o, int v, const char *fmt, ...)
  {
 @@ -177,28 +200,30 @@ static void output(struct merge_options *o, int v, const char *fmt, ...)
  
  static void output_commit_title(struct merge_options *o, struct commit *commit)
  {
 -	int i;
 -	flush_output(o);
 -	for (i = o->call_depth; i--;)
 -		fputs("  ", stdout);
 +	strbuf_addchars(&o->obuf, ' ', o->call_depth * 2);
  	if (commit->util)
 -		printf("virtual %s\n", merge_remote_util(commit)->name);
 +		strbuf_addf(&o->obuf, "virtual %s\n",
 +			merge_remote_util(commit)->name);
  	else {
 -		printf("%s ", find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
 +		strbuf_addf(&o->obuf, "%s ",
 +			find_unique_abbrev(commit->object.oid.hash,
 +				DEFAULT_ABBREV));
  		if (parse_commit(commit) != 0)
 -			printf(_("(bad commit)\n"));
 +			strbuf_addf(&o->obuf, _("(bad commit)\n"));
  		else {
  			const char *title;
  			const char *msg = get_commit_buffer(commit, NULL);
  			int len = find_commit_subject(msg, &title);
  			if (len)
 -				printf("%.*s\n", len, title);
 +				strbuf_addf(&o->obuf, "%.*s\n", len, title);
  			unuse_commit_buffer(commit, msg);
  		}
  	}
 +	flush_output(o);
  }
  
 -static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
 +static int add_cacheinfo(struct merge_options *o,
 +		unsigned int mode, const unsigned char *sha1,
  		const char *path, int stage, int refresh, int options)
  {
  	struct cache_entry *ce;
 @@ -206,7 +231,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
  			      (refresh ? (CE_MATCH_REFRESH |
  					  CE_MATCH_IGNORE_MISSING) : 0 ));
  	if (!ce)
 -		return error(_("addinfo_cache failed for path '%s'"), path);
 +		return err(o, _("addinfo_cache failed for path '%s'"), path);
  	return add_cache_entry(ce, options);
  }
  
 @@ -267,7 +292,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
  
  	if (!cache_tree_fully_valid(active_cache_tree) &&
  	    cache_tree_update(&the_index, 0) < 0) {
 -		error(_("error building trees"));
 +		err(o, _("error building trees"));
  		return NULL;
  	}
  
 @@ -535,7 +560,8 @@ static struct string_list *get_renames(struct merge_options *o,
  	return renames;
  }
  
 -static int update_stages(const char *path, const struct diff_filespec *o,
 +static int update_stages(struct merge_options *opt, const char *path,
 +			 const struct diff_filespec *o,
  			 const struct diff_filespec *a,
  			 const struct diff_filespec *b)
  {
 @@ -550,17 +576,19 @@ static int update_stages(const char *path, const struct diff_filespec *o,
  	 */
  	int clear = 1;
  	int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
 -	int ret = 0;
 -
  	if (clear)
 -		ret = remove_file_from_cache(path);
 -	if (!ret && o)
 -		ret = add_cacheinfo(o->mode, o->sha1, path, 1, 0, options);
 -	if (!ret && a)
 -		ret = add_cacheinfo(a->mode, a->sha1, path, 2, 0, options);
 -	if (!ret && b)
 -		ret = add_cacheinfo(b->mode, b->sha1, path, 3, 0, options);
 -	return ret;
 +		if (remove_file_from_cache(path))
 +			return -1;
 +	if (o)
 +		if (add_cacheinfo(opt, o->mode, o->sha1, path, 1, 0, options))
 +			return -1;
 +	if (a)
 +		if (add_cacheinfo(opt, a->mode, a->sha1, path, 2, 0, options))
 +			return -1;
 +	if (b)
 +		if (add_cacheinfo(opt, b->mode, b->sha1, path, 3, 0, options))
 +			return -1;
 +	return 0;
  }
  
  static void update_entry(struct stage_data *entry,
 @@ -658,24 +686,22 @@ static int was_tracked(const char *path)
  {
  	int pos = cache_name_pos(path, strlen(path));
  
 -	/* cache_name_pos() looks for stage == 0, so pos may be < 0 */
 -	if (pos < 0)
 -		pos = -1 - pos;
 -	while (pos < active_nr &&
 -	       !strcmp(path, active_cache[pos]->name)) {
 +	if (pos >= 0)
 +		return pos < active_nr;
 +	/*
 +	 * cache_name_pos() looks for stage == 0, even if we did not ask for
 +	 * it. Let's look for stage == 2 now.
 +	 */
 +	for (pos = -1 - pos; pos < active_nr &&
 +	     !strcmp(path, active_cache[pos]->name); pos++)
  		/*
  		 * If stage #0, it is definitely tracked.
  		 * If it has stage #2 then it was tracked
  		 * before this merge started.  All other
  		 * cases the path was not tracked.
  		 */
 -		switch (ce_stage(active_cache[pos])) {
 -		case 0:
 -		case 2:
 +		if (ce_stage(active_cache[pos]) == 2)
  			return 1;
 -		}
 -		pos++;
 -	}
  	return 0;
  }
  
 @@ -712,8 +738,8 @@ static int make_room_for_path(struct merge_options *o, const char *path)
  	if (status) {
  		if (status == SCLD_EXISTS)
  			/* something else exists */
 -			return error(msg, path, _(": perhaps a D/F conflict?"));
 -		return error(msg, path, "");
 +			return err(o, msg, path, _(": perhaps a D/F conflict?"));
 +		return err(o, msg, path, "");
  	}
  
  	/*
 @@ -721,7 +747,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
  	 * tracking it.
  	 */
  	if (would_lose_untracked(path))
 -		return error(_("refusing to lose untracked file at '%s'"),
 +		return err(o, _("refusing to lose untracked file at '%s'"),
  			     path);
  
  	/* Successful unlink is good.. */
 @@ -731,7 +757,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
  	if (errno == ENOENT)
  		return 0;
  	/* .. but not some other error (who really cares what?) */
 -	return error(msg, path, _(": perhaps a D/F conflict?"));
 +	return err(o, msg, path, _(": perhaps a D/F conflict?"));
  }
  
  static int update_file_flags(struct merge_options *o,
 @@ -763,9 +789,9 @@ static int update_file_flags(struct merge_options *o,
  
  		buf = read_sha1_file(sha, &type, &size);
  		if (!buf)
 -			return error(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
 +			return err(o, _("cannot read object %s '%s'"), sha1_to_hex(sha), path);
  		if (type != OBJ_BLOB) {
 -			ret = error(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
 +			ret = err(o, _("blob expected for %s '%s'"), sha1_to_hex(sha), path);
  			goto free_buf;
  		}
  		if (S_ISREG(mode)) {
 @@ -789,7 +815,8 @@ static int update_file_flags(struct merge_options *o,
  				mode = 0666;
  			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
  			if (fd < 0) {
 -				ret = error_errno(_("failed to open '%s'"), path);
 +				ret = err(o, _("failed to open '%s': %s"),
 +					path, strerror(errno));
  				goto free_buf;
  			}
  			write_in_full(fd, buf, size);
 @@ -799,17 +826,18 @@ static int update_file_flags(struct merge_options *o,
  			safe_create_leading_directories_const(path);
  			unlink(path);
  			if (symlink(lnk, path))
 -				ret = error_errno(_("failed to symlink '%s'"), path);
 +				ret = err(o, _("failed to symlink '%s': %s"),
 +					path, strerror(errno));
  			free(lnk);
  		} else
 -			ret = error_errno(_("do not know what to do with %06o %s '%s'"),
 +			ret = err(o, _("do not know what to do with %06o %s '%s'"),
  				mode, sha1_to_hex(sha), path);
 -free_buf:
 + free_buf:
  		free(buf);
  	}
   update_index:
  	if (!ret && update_cache)
 -		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 +		add_cacheinfo(o, mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
  	return ret;
  }
  
 @@ -942,11 +970,11 @@ static int merge_file_1(struct merge_options *o,
  						  branch1, branch2);
  
  			if ((merge_status < 0) || !result_buf.ptr)
 -				ret = error(_("Failed to execute internal merge"));
 +				ret = err(o, _("Failed to execute internal merge"));
  
  			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
  					    blob_type, result->sha))
 -				ret = error(_("Unable to add %s to database"),
 +				ret = err(o, _("Unable to add %s to database"),
  					a->path);
  
  			free(result_buf.ptr);
 @@ -964,7 +992,7 @@ static int merge_file_1(struct merge_options *o,
  			if (!sha_eq(a->sha1, b->sha1))
  				result->clean = 0;
  		} else
 -			die(_("BUG: unsupported object type in the tree"));
 +			die("BUG: unsupported object type in the tree");
  	}
  
  	return 0;
 @@ -1090,7 +1118,6 @@ static int conflict_rename_delete(struct merge_options *o,
  	const unsigned char *b_sha = NULL;
  	int a_mode = 0;
  	int b_mode = 0;
 -	int ret = 0;
  
  	if (rename_branch == o->branch1) {
  		a_sha = dest->sha1;
 @@ -1100,22 +1127,19 @@ static int conflict_rename_delete(struct merge_options *o,
  		b_mode = dest->mode;
  	}
  
 -	ret = handle_change_delete(o,
 +	if (handle_change_delete(o,
  			     o->call_depth ? orig->path : dest->path,
  			     orig->sha1, orig->mode,
  			     a_sha, a_mode,
  			     b_sha, b_mode,
 -			     _("rename"), _("renamed"));
 -	if (ret < 0)
 -		return ret;
 +			     _("rename"), _("renamed")))
 +		return -1;
  	if (o->call_depth)
 -		ret = remove_file_from_cache(dest->path);
 +		return remove_file_from_cache(dest->path);
  	else
 -		ret = update_stages(dest->path, NULL,
 +		return update_stages(o, dest->path, NULL,
  			      rename_branch == o->branch1 ? dest : NULL,
  			      rename_branch == o->branch1 ? NULL : dest);
 -
 -	return ret;
  }
  
  static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 @@ -1156,8 +1180,8 @@ static int handle_file(struct merge_options *o,
  	add = filespec_from_entry(&other, dst_entry, stage ^ 1);
  	if (add) {
  		char *add_name = unique_path(o, rename->path, other_branch);
 -		if ((ret = update_file(o, 0, add->sha1, add->mode, add_name)))
 -			return ret;
 +		if (update_file(o, 0, add->sha1, add->mode, add_name))
 +			return -1;
  
  		remove_file(o, 0, rename->path, 0);
  		dst_name = unique_path(o, rename->path, cur_branch);
 @@ -1171,9 +1195,9 @@ static int handle_file(struct merge_options *o,
  	if ((ret = update_file(o, 0, rename->sha1, rename->mode, dst_name)))
  		; /* fall through, do allow dst_name to be released */
  	else if (stage == 2)
 -		ret = update_stages(rename->path, NULL, rename, add);
 +		ret = update_stages(o, rename->path, NULL, rename, add);
  	else
 -		ret = update_stages(rename->path, NULL, add, rename);
 +		ret = update_stages(o, rename->path, NULL, add, rename);
  
  	if (dst_name != rename->path)
  		free(dst_name);
 @@ -1188,7 +1212,6 @@ static int conflict_rename_rename_1to2(struct merge_options *o,
  	struct diff_filespec *one = ci->pair1->one;
  	struct diff_filespec *a = ci->pair1->two;
  	struct diff_filespec *b = ci->pair2->two;
 -	int ret = 0;
  
  	output(o, 1, _("CONFLICT (rename/rename): "
  	       "Rename \"%s\"->\"%s\" in branch \"%s\" "
 @@ -1201,12 +1224,12 @@ static int conflict_rename_rename_1to2(struct merge_options *o,
  		struct diff_filespec other;
  		struct diff_filespec *add;
  
 -		if ((ret = merge_file_one(o, one->path,
 +		if (merge_file_one(o, one->path,
  				 one->sha1, one->mode,
  				 a->sha1, a->mode,
  				 b->sha1, b->mode,
 -				 ci->branch1, ci->branch2, &mfi)))
 -			return ret;
 +				 ci->branch1, ci->branch2, &mfi))
 +			return -1;
  
  		/*
  		 * FIXME: For rename/add-source conflicts (if we could detect
 @@ -1214,8 +1237,8 @@ static int conflict_rename_rename_1to2(struct merge_options *o,
  		 * pathname and then either rename the add-source file to that
  		 * unique path, or use that unique path instead of src here.
  		 */
 -		if ((ret = update_file(o, 0, mfi.sha, mfi.mode, one->path)))
 -			return ret;
 +		if (update_file(o, 0, mfi.sha, mfi.mode, one->path))
 +			return -1;
  
  		/*
  		 * Above, we put the merged content at the merge-base's
 @@ -1227,27 +1250,22 @@ static int conflict_rename_rename_1to2(struct merge_options *o,
  		 */
  		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
  		if (add) {
 -			if ((ret = update_file(o, 0, add->sha1, add->mode,
 -					a->path)))
 -				return ret;
 +			if (update_file(o, 0, add->sha1, add->mode, a->path))
 +				return -1;
  		}
  		else
  			remove_file_from_cache(a->path);
  		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
  		if (add) {
 -			if ((ret = update_file(o, 0, add->sha1, add->mode,
 -					b->path)))
 -				return ret;
 +			if (update_file(o, 0, add->sha1, add->mode, b->path))
 +				return -1;
  		}
  		else
  			remove_file_from_cache(b->path);
 -	} else {
 -		if ((ret = handle_file(o, a, 2, ci)) ||
 -		    (ret = handle_file(o, b, 3, ci)))
 -			return ret;
 -	}
 +	} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
 +		return -1;
  
 -	return ret;
 +	return 0;
  }
  
  static int conflict_rename_rename_2to1(struct merge_options *o,
 @@ -1272,13 +1290,13 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
  	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
  	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
  
 -	if ((ret = merge_file_special_markers(o, a, c1, &ci->ren1_other,
 +	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
  					    o->branch1, c1->path,
 -					    o->branch2, ci->ren1_other.path, &mfi_c1)) ||
 -	    (ret = merge_file_special_markers(o, b, &ci->ren2_other, c2,
 +					    o->branch2, ci->ren1_other.path, &mfi_c1) ||
 +	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
  					    o->branch1, ci->ren2_other.path,
 -					    o->branch2, c2->path, &mfi_c2)))
 -		return ret;
 +					    o->branch2, c2->path, &mfi_c2))
 +		return -1;
  
  	if (o->call_depth) {
  		/*
 @@ -1314,7 +1332,7 @@ static int process_renames(struct merge_options *o,
  			   struct string_list *a_renames,
  			   struct string_list *b_renames)
  {
 -	int clean_merge = 1, i, j, ret;
 +	int clean_merge = 1, i, j;
  	struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
  	struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
  	const struct rename *sre;
 @@ -1490,14 +1508,13 @@ static int process_renames(struct merge_options *o,
  				 * update_file_flags() instead of
  				 * update_file().
  				 */
 -				ret = update_file_flags(o,
 +				if (update_file_flags(o,
  						  ren1->pair->two->sha1,
  						  ren1->pair->two->mode,
  						  ren1_dst,
  						  1, /* update_cache */
 -						  0  /* update_wd    */);
 -				if (ret)
 -					clean_merge = ret;
 +						  0  /* update_wd    */))
 +					clean_merge = -1;
  			} else if (!sha_eq(dst_other.sha1, null_sha1)) {
  				clean_merge = 0;
  				try_merge = 1;
 @@ -1507,22 +1524,22 @@ static int process_renames(struct merge_options *o,
  				       ren1_dst, branch2);
  				if (o->call_depth) {
  					struct merge_file_info mfi;
 -					if ((ret = merge_file_one(o, ren1_dst, null_sha1, 0,
 +					if (merge_file_one(o, ren1_dst, null_sha1, 0,
  							 ren1->pair->two->sha1, ren1->pair->two->mode,
  							 dst_other.sha1, dst_other.mode,
 -							 branch1, branch2, &mfi))) {
 -						clean_merge = ret;
 +							 branch1, branch2, &mfi)) {
 +						clean_merge = -1;
  						goto cleanup_and_return;
  					}
  					output(o, 1, _("Adding merged %s"), ren1_dst);
 -					if ((ret = update_file(o, 0, mfi.sha, mfi.mode, ren1_dst)))
 -						clean_merge = ret;
 +					if (update_file(o, 0, mfi.sha, mfi.mode, ren1_dst))
 +						clean_merge = -1;
  					try_merge = 0;
  				} else {
  					char *new_path = unique_path(o, ren1_dst, branch2);
  					output(o, 1, _("Adding as %s instead"), new_path);
 -					if ((ret = update_file(o, 0, dst_other.sha1, dst_other.mode, new_path)))
 -						clean_merge = ret;
 +					if (update_file(o, 0, dst_other.sha1, dst_other.mode, new_path))
 +						clean_merge = -1;
  					free(new_path);
  				}
  			} else
 @@ -1568,23 +1585,25 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
  	return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
  }
  
 -static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
 +static int read_sha1_strbuf(struct merge_options *o,
 +	const unsigned char *sha1, struct strbuf *dst)
  {
  	void *buf;
  	enum object_type type;
  	unsigned long size;
  	buf = read_sha1_file(sha1, &type, &size);
  	if (!buf)
 -		return error(_("cannot read object %s"), sha1_to_hex(sha1));
 +		return err(o, _("cannot read object %s"), sha1_to_hex(sha1));
  	if (type != OBJ_BLOB) {
  		free(buf);
 -		return error(_("object %s is not a blob"), sha1_to_hex(sha1));
 +		return err(o, _("object %s is not a blob"), sha1_to_hex(sha1));
  	}
  	strbuf_attach(dst, buf, size, size + 1);
  	return 0;
  }
  
 -static int blob_unchanged(const unsigned char *o_sha,
 +static int blob_unchanged(struct merge_options *opt,
 +			  const unsigned char *o_sha,
  			  unsigned o_mode,
  			  const unsigned char *a_sha,
  			  unsigned a_mode,
 @@ -1602,7 +1621,7 @@ static int blob_unchanged(const unsigned char *o_sha,
  		return 0;
  
  	assert(o_sha && a_sha);
 -	if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
 +	if (read_sha1_strbuf(opt, o_sha, &o) || read_sha1_strbuf(opt, a_sha, &a))
  		goto error_return;
  	/*
  	 * Note: binary | is used so that both renormalizations are
 @@ -1645,7 +1664,6 @@ static int merge_content(struct merge_options *o,
  	struct merge_file_info mfi;
  	struct diff_filespec one, a, b;
  	unsigned df_conflict_remains = 0;
 -	int ret;
  
  	if (!o_sha) {
  		reason = _("add/add");
 @@ -1675,10 +1693,10 @@ static int merge_content(struct merge_options *o,
  		if (dir_in_way(path, !o->call_depth))
  			df_conflict_remains = 1;
  	}
 -	if ((ret = merge_file_special_markers(o, &one, &a, &b,
 +	if (merge_file_special_markers(o, &one, &a, &b,
  					 o->branch1, path1,
 -					 o->branch2, path2, &mfi)))
 -		return ret;
 +					 o->branch2, path2, &mfi))
 +		return -1;
  
  	if (mfi.clean && !df_conflict_remains &&
  	    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
 @@ -1692,7 +1710,7 @@ static int merge_content(struct merge_options *o,
  		 */
  		path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
  		if (!path_renamed_outside_HEAD) {
 -			add_cacheinfo(mfi.mode, mfi.sha, path,
 +			add_cacheinfo(o, mfi.mode, mfi.sha, path,
  				      0, (!o->call_depth), 0);
  			return mfi.clean;
  		}
 @@ -1705,8 +1723,8 @@ static int merge_content(struct merge_options *o,
  		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
  				reason, path);
  		if (rename_conflict_info && !df_conflict_remains)
 -			if ((ret = update_stages(path, &one, &a, &b)))
 -				return ret;
 +			if (update_stages(o, path, &one, &a, &b))
 +				return -1;
  	}
  
  	if (df_conflict_remains) {
 @@ -1715,42 +1733,39 @@ static int merge_content(struct merge_options *o,
  			remove_file_from_cache(path);
  		} else {
  			if (!mfi.clean) {
 -				if ((ret = update_stages(path, &one, &a, &b)))
 -					return ret;
 +				if (update_stages(o, path, &one, &a, &b))
 +					return -1;
  			} else {
  				int file_from_stage2 = was_tracked(path);
  				struct diff_filespec merged;
  				hashcpy(merged.sha1, mfi.sha);
  				merged.mode = mfi.mode;
  
 -				if ((ret = update_stages(path, NULL,
 +				if (update_stages(o, path, NULL,
  					      file_from_stage2 ? &merged : NULL,
 -					      file_from_stage2 ? NULL : &merged)))
 -					return ret;
 +					      file_from_stage2 ? NULL : &merged))
 +					return -1;
  			}
  
  		}
  		new_path = unique_path(o, path, rename_conflict_info->branch1);
  		output(o, 1, _("Adding as %s instead"), new_path);
 -		if ((ret = update_file(o, 0, mfi.sha, mfi.mode, new_path))) {
 +		if (update_file(o, 0, mfi.sha, mfi.mode, new_path)) {
  			free(new_path);
 -			return ret;
 +			return -1;
  		}
  		free(new_path);
  		mfi.clean = 0;
 -	} else {
 -		if ((ret = update_file(o, mfi.clean, mfi.sha, mfi.mode, path)))
 -			return ret;
 -	}
 +	} else if (update_file(o, mfi.clean, mfi.sha, mfi.mode, path))
 +		return -1;
  	return mfi.clean;
 -
  }
  
  /* Per entry merge function */
  static int process_entry(struct merge_options *o,
  			 const char *path, struct stage_data *entry)
  {
 -	int clean_merge = 1, ret;
 +	int clean_merge = 1;
  	int normalize = o->renormalize;
  	unsigned o_mode = entry->stages[1].mode;
  	unsigned a_mode = entry->stages[2].mode;
 @@ -1771,23 +1786,21 @@ static int process_entry(struct merge_options *o,
  			break;
  		case RENAME_DELETE:
  			clean_merge = 0;
 -			if ((ret = conflict_rename_delete(o,
 +			if (conflict_rename_delete(o,
  					       conflict_info->pair1,
  					       conflict_info->branch1,
 -					       conflict_info->branch2)))
 -				clean_merge = ret;
 +					       conflict_info->branch2))
 +				clean_merge = -1;
  			break;
  		case RENAME_ONE_FILE_TO_TWO:
  			clean_merge = 0;
 -			if ((ret = conflict_rename_rename_1to2(o,
 -					conflict_info)))
 -				clean_merge = ret;
 +			if (conflict_rename_rename_1to2(o, conflict_info))
 +				clean_merge = -1;
  			break;
  		case RENAME_TWO_FILES_TO_ONE:
  			clean_merge = 0;
 -			if ((ret = conflict_rename_rename_2to1(o,
 -					conflict_info)))
 -				clean_merge = ret;
 +			if (conflict_rename_rename_2to1(o, conflict_info))
 +				clean_merge = -1;
  			break;
  		default:
  			entry->processed = 0;
 @@ -1796,8 +1809,8 @@ static int process_entry(struct merge_options *o,
  	} else if (o_sha && (!a_sha || !b_sha)) {
  		/* Case A: Deleted in one */
  		if ((!a_sha && !b_sha) ||
 -		    (!b_sha && blob_unchanged(o_sha, o_mode, a_sha, a_mode, normalize, path)) ||
 -		    (!a_sha && blob_unchanged(o_sha, o_mode, b_sha, b_mode, normalize, path))) {
 +		    (!b_sha && blob_unchanged(o, o_sha, o_mode, a_sha, a_mode, normalize, path)) ||
 +		    (!a_sha && blob_unchanged(o, o_sha, o_mode, b_sha, b_mode, normalize, path))) {
  			/* Deleted in both or deleted in one and
  			 * unchanged in the other */
  			if (a_sha)
 @@ -1807,9 +1820,9 @@ static int process_entry(struct merge_options *o,
  		} else {
  			/* Modify/delete; deleted side may have put a directory in the way */
  			clean_merge = 0;
 -			if ((ret = handle_modify_delete(o, path, o_sha, o_mode,
 -					a_sha, a_mode, b_sha, b_mode)))
 -				clean_merge = ret;
 +			if (handle_modify_delete(o, path, o_sha, o_mode,
 +						 a_sha, a_mode, b_sha, b_mode))
 +				clean_merge = -1;
  		}
  	} else if ((!o_sha && a_sha && !b_sha) ||
  		   (!o_sha && !a_sha && b_sha)) {
 @@ -1841,18 +1854,16 @@ static int process_entry(struct merge_options *o,
  			output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
  			       "Adding %s as %s"),
  			       conf, path, other_branch, path, new_path);
 -			ret = update_file(o, 0, sha, mode, new_path);
 -			if (ret)
 -				clean_merge = ret;
 +			if (update_file(o, 0, sha, mode, new_path))
 +				clean_merge = -1;
  			else if (o->call_depth)
  				remove_file_from_cache(path);
  			free(new_path);
  		} else {
  			output(o, 2, _("Adding %s"), path);
  			/* do not overwrite file if already present */
 -			if ((ret = update_file_flags(o, sha, mode, path, 1,
 -					!a_sha)))
 -				clean_merge = ret;
 +			if (update_file_flags(o, sha, mode, path, 1, !a_sha))
 +				clean_merge = -1;
  		}
  	} else if (a_sha && b_sha) {
  		/* Case C: Added in both (check for same permissions) and */
 @@ -1867,7 +1878,7 @@ static int process_entry(struct merge_options *o,
  		 */
  		remove_file(o, 1, path, !a_mode);
  	} else
 -		return error(_("Fatal merge failure, shouldn't happen."));
 +		die("BUG: fatal merge failure, shouldn't happen.");
  
  	return clean_merge;
  }
 @@ -1895,7 +1906,7 @@ int merge_trees(struct merge_options *o,
  
  	if (code != 0) {
  		if (show(o, 4) || o->call_depth)
 -			error(_("merging of trees %s and %s failed"),
 +			err(o, _("merging of trees %s and %s failed"),
  			    oid_to_hex(&head->object.oid),
  			    oid_to_hex(&merge->object.oid));
  		return -1;
 @@ -1930,7 +1941,7 @@ int merge_trees(struct merge_options *o,
  		for (i = 0; i < entries->nr; i++) {
  			struct stage_data *e = entries->items[i].util;
  			if (!e->processed)
 -				die(_("BUG: unprocessed path??? %s"),
 +				die("BUG: unprocessed path??? %s",
  				    entries->items[i].string);
  		}
  
 @@ -2029,7 +2040,7 @@ int merge_recursive(struct merge_options *o,
  		o->call_depth--;
  
  		if (!merged_common_ancestors)
 -			return error(_("merge returned no commit"));
 +			return err(o, _("merge returned no commit"));
  	}
  
  	discard_cache();
 @@ -2039,6 +2050,7 @@ int merge_recursive(struct merge_options *o,
  	o->ancestor = "merged common ancestors";
  	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
  			    &mrtree);
 +	flush_output(o);
  	if (clean < 0)
  		return clean;
  
 @@ -2047,7 +2059,8 @@ int merge_recursive(struct merge_options *o,
  		commit_list_insert(h1, &(*result)->parents);
  		commit_list_insert(h2, &(*result)->parents->next);
  	}
 -	flush_output(o);
 +	if (o->buffer_output < 2)
 +		strbuf_release(&o->obuf);
  	if (show(o, 2))
  		diff_warn_rename_limit("merge.renamelimit",
  				       o->needed_rename_limit, 0);
 @@ -2088,7 +2101,7 @@ int merge_recursive_generic(struct merge_options *o,
  		for (i = 0; i < num_base_list; ++i) {
  			struct commit *base;
  			if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))
 -				return error(_("Could not parse object '%s'"),
 +				return err(o, _("Could not parse object '%s'"),
  					sha1_to_hex(base_list[i]));
  			commit_list_insert(base, &ca);
  		}
 @@ -2102,7 +2115,7 @@ int merge_recursive_generic(struct merge_options *o,
  
  	if (active_cache_changed &&
  	    write_locked_index(&the_index, lock, COMMIT_LOCK))
 -		return error(_("Unable to write index."));
 +		return err(o, _("Unable to write index."));
  
  	return clean ? 0 : 1;
  }
 diff --git a/merge-recursive.h b/merge-recursive.h
 index 52f0201..407d4fc 100644
 --- a/merge-recursive.h
 +++ b/merge-recursive.h
 @@ -13,7 +13,7 @@ struct merge_options {
  		MERGE_RECURSIVE_THEIRS
  	} recursive_variant;
  	const char *subtree_shift;
 -	unsigned buffer_output : 1;
 +	unsigned buffer_output : 2; /* 1: output at end, 2: keep buffered */
  	unsigned renormalize : 1;
  	long xdl_opts;
  	int verbosity;
 diff --git a/sequencer.c b/sequencer.c
 index 13b794a..8ceeb1b 100644
 --- a/sequencer.c
 +++ b/sequencer.c
 @@ -293,6 +293,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  	clean = merge_trees(&o,
  			    head_tree,
  			    next_tree, base_tree, &result);
 +	strbuf_release(&o.obuf);
  	if (clean < 0)
  		return clean;
  
 diff --git a/sha1_file.c b/sha1_file.c
 index aa7006c..5085fe0 100644
 --- a/sha1_file.c
 +++ b/sha1_file.c
 @@ -795,7 +795,7 @@ void close_all_packs(void)
  
  	for (p = packed_git; p; p = p->next)
  		if (p->do_not_close)
 -			die("BUG: Want to close pack marked 'do-not-close'");
 +			die("BUG: want to close pack marked 'do-not-close'");
  		else
  			close_pack(p);
  }
 diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
 index 3159956..b281d6f 100755
 --- a/t/t5520-pull.sh
 +++ b/t/t5520-pull.sh
 @@ -255,6 +255,36 @@ test_expect_success '--rebase' '
  	test new = "$(git show HEAD:file2)"
  '
  
 +test_expect_success '--rebase with conflicts shows advice' '
 +	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
 +	git checkout -b seq &&
 +	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&
 +	git add seq.txt &&
 +	test_tick &&
 +	git commit -m "Add seq.txt" &&
 +	printf "6\\n" >>seq.txt &&
 +	test_tick &&
 +	git commit -m "Append to seq.txt" seq.txt &&
 +	git checkout -b with-conflicts HEAD^ &&
 +	printf "conflicting\\n" >>seq.txt &&
 +	test_tick &&
 +	git commit -m "Create conflict" seq.txt &&
 +	test_must_fail git pull --rebase . seq 2>err >out &&
 +	grep "When you have resolved this problem" out
 +'
 +test_expect_success 'failed --rebase shows advice' '
 +	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
 +	git checkout -b diverging &&
 +	test_commit attributes .gitattributes "* text=auto" attrs &&
 +	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
 +	git update-index --cacheinfo 0644 $sha1 file &&
 +	git commit -m v1-with-cr &&
 +	git checkout -f -b fails-to-rebase HEAD^ &&
 +	test_commit v2-without-cr file "2" file2-lf &&
 +	test_must_fail git pull --rebase . diverging 2>err >out &&
 +	grep "When you have resolved this problem" out
 +'
 +
  test_expect_success '--rebase fails with multiple branches' '
  	git reset --hard before-rebase &&
  	test_must_fail git pull --rebase . copy master 2>err &&
 diff --git a/wt-status.c b/wt-status.c
 index 311ae7c..75c1162 100644
 --- a/wt-status.c
 +++ b/wt-status.c
 @@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
  	case 7:
  		return _("both modified:");
  	default:
 -		die(_("BUG: unhandled unmerged status %x"), stagemask);
 +		die("BUG: unhandled unmerged status %x", stagemask);
  	}
  }
  
 @@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
  	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
  	what = wt_status_diff_status_string(status);
  	if (!what)
 -		die(_("BUG: unhandled diff status %c"), status);
 +		die("BUG: unhandled diff status %c", status);
  	len = label_width - utf8_strwidth(what);
  	assert(len >= 0);
  	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)

-- 
2.9.0.280.g32e2a70

base-commit: cf4c2cfe52be5bd973a4838f73a35d3959ce2f43

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v2 01/17] Verify that `git pull --rebase` shows the helpful advice when failing
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 02/17] Report bugs consistently Johannes Schindelin
                     ` (17 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5520-pull.sh | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 3159956..b281d6f 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -255,6 +255,36 @@ test_expect_success '--rebase' '
 	test new = "$(git show HEAD:file2)"
 '
 
+test_expect_success '--rebase with conflicts shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b seq &&
+	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&
+	git add seq.txt &&
+	test_tick &&
+	git commit -m "Add seq.txt" &&
+	printf "6\\n" >>seq.txt &&
+	test_tick &&
+	git commit -m "Append to seq.txt" seq.txt &&
+	git checkout -b with-conflicts HEAD^ &&
+	printf "conflicting\\n" >>seq.txt &&
+	test_tick &&
+	git commit -m "Create conflict" seq.txt &&
+	test_must_fail git pull --rebase . seq 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+test_expect_success 'failed --rebase shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b diverging &&
+	test_commit attributes .gitattributes "* text=auto" attrs &&
+	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
+	git update-index --cacheinfo 0644 $sha1 file &&
+	git commit -m v1-with-cr &&
+	git checkout -f -b fails-to-rebase HEAD^ &&
+	test_commit v2-without-cr file "2" file2-lf &&
+	test_must_fail git pull --rebase . diverging 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+
 test_expect_success '--rebase fails with multiple branches' '
 	git reset --hard before-rebase &&
 	test_must_fail git pull --rebase . copy master 2>err &&
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 02/17] Report bugs consistently
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 01/17] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 13:05     ` Jakub Narębski
  2016-07-06 15:30     ` Duy Nguyen
  2016-07-05 11:23   ` [PATCH v2 03/17] Avoid translating bug messages Johannes Schindelin
                     ` (16 subsequent siblings)
  18 siblings, 2 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

The vast majority of error messages in Git's source code which report a
bug use the convention to prefix the message with "BUG:".

As part of cleaning up merge-recursive to stop die()ing except in case of
detected bugs, let's just make the remainder of the bug reports consistent
with the de facto rule.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/ls-files.c     |  3 ++-
 builtin/update-index.c |  2 +-
 grep.c                 |  8 ++++----
 imap-send.c            |  4 ++--
 merge-recursive.c      | 15 +++++++--------
 sha1_file.c            |  4 ++--
 trailer.c              |  2 +-
 transport.c            |  2 +-
 wt-status.c            |  4 ++--
 9 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f02e3d2..00ea91a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
 				 */
 				pos = cache_name_pos(ent->name, ent->len);
 				if (0 <= pos)
-					die("bug in show-killed-files");
+					die("BUG: killed-file %.*s not found",
+						ent->len, ent->name);
 				pos = -pos - 1;
 				while (pos < active_nr &&
 				       ce_stage(active_cache[pos]))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 6cdfd5f..ba04b19 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -1146,7 +1146,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
 		break;
 	default:
-		die("Bug: bad untracked_cache value: %d", untracked_cache);
+		die("BUG: bad untracked_cache value: %d", untracked_cache);
 	}
 
 	if (active_cache_changed) {
diff --git a/grep.c b/grep.c
index 1e15b62..f1ca0a0 100644
--- a/grep.c
+++ b/grep.c
@@ -643,10 +643,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 	for (p = opt->header_list; p; p = p->next) {
 		if (p->token != GREP_PATTERN_HEAD)
-			die("bug: a non-header pattern in grep header list.");
+			die("BUG: a non-header pattern in grep header list.");
 		if (p->field < GREP_HEADER_FIELD_MIN ||
 		    GREP_HEADER_FIELD_MAX <= p->field)
-			die("bug: unknown header field %d", p->field);
+			die("BUG: unknown header field %d", p->field);
 		compile_regexp(p, opt);
 	}
 
@@ -659,7 +659,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 		h = compile_pattern_atom(&pp);
 		if (!h || pp != p->next)
-			die("bug: malformed header expr");
+			die("BUG: malformed header expr");
 		if (!header_group[p->field]) {
 			header_group[p->field] = h;
 			continue;
@@ -1464,7 +1464,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 		case GREP_BINARY_TEXT:
 			break;
 		default:
-			die("bug: unknown binary handling mode");
+			die("BUG: unknown binary handling mode");
 		}
 	}
 
diff --git a/imap-send.c b/imap-send.c
index 938c691..369f72a 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -506,12 +506,12 @@ static char *next_arg(char **s)
 
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
-	int ret;
+	int ret = -1;
 	va_list va;
 
 	va_start(va, fmt);
 	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-		die("Fatal: buffer too small. Please report a bug.");
+		die("BUG: buffer too small (%d < %d)", ret, blen);
 	va_end(va);
 	return ret;
 }
diff --git a/merge-recursive.c b/merge-recursive.c
index 65cb5d6..9849439 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -259,7 +259,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 				fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
 					(int)ce_namelen(ce), ce->name);
 		}
-		die("Bug in merge-recursive.c");
+		die("BUG: unmerged index entries in merge-recursive.c");
 	}
 
 	if (!active_cache_tree)
@@ -955,9 +955,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 
 			if (!sha_eq(a->sha1, b->sha1))
 				result.clean = 0;
-		} else {
-			die(_("unsupported object type in the tree"));
-		}
+		} else
+			die(_("BUG: unsupported object type in the tree"));
 	}
 
 	return result;
@@ -1343,7 +1342,7 @@ static int process_renames(struct merge_options *o,
 			const char *ren2_dst = ren2->pair->two->path;
 			enum rename_type rename_type;
 			if (strcmp(ren1_src, ren2_src) != 0)
-				die("ren1_src != ren2_src");
+				die("BUG: ren1_src != ren2_src");
 			ren2->dst_entry->processed = 1;
 			ren2->processed = 1;
 			if (strcmp(ren1_dst, ren2_dst) != 0) {
@@ -1377,7 +1376,7 @@ static int process_renames(struct merge_options *o,
 			ren2 = lookup->util;
 			ren2_dst = ren2->pair->two->path;
 			if (strcmp(ren1_dst, ren2_dst) != 0)
-				die("ren1_dst != ren2_dst");
+				die("BUG: ren1_dst != ren2_dst");
 
 			clean_merge = 0;
 			ren2->processed = 1;
@@ -1795,7 +1794,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("Fatal merge failure, shouldn't happen."));
+		die(_("BUG: fatal merge failure, shouldn't happen."));
 
 	return clean_merge;
 }
@@ -1853,7 +1852,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("Unprocessed path??? %s"),
+				die(_("BUG: unprocessed path??? %s"),
 				    entries->items[i].string);
 		}
 
diff --git a/sha1_file.c b/sha1_file.c
index d5e1121..5085fe0 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -795,7 +795,7 @@ void close_all_packs(void)
 
 	for (p = packed_git; p; p = p->next)
 		if (p->do_not_close)
-			die("BUG! Want to close pack marked 'do-not-close'");
+			die("BUG: want to close pack marked 'do-not-close'");
 		else
 			close_pack(p);
 }
@@ -2330,7 +2330,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
 		if (data)
-			die("BUG in unpack_entry: left loop at a valid delta");
+			die("BUG: unpack_entry: left loop at a valid delta");
 		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
diff --git a/trailer.c b/trailer.c
index 8e48a5c..c6ea9ac 100644
--- a/trailer.c
+++ b/trailer.c
@@ -562,7 +562,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 		break;
 	default:
-		die("internal bug in trailer.c");
+		die("BUG: trailer.c: unhandled type %d", type);
 	}
 	return 0;
 }
diff --git a/transport.c b/transport.c
index 095e61f..52bf997 100644
--- a/transport.c
+++ b/transport.c
@@ -563,7 +563,7 @@ void transport_take_over(struct transport *transport,
 	struct git_transport_data *data;
 
 	if (!transport->smart_options)
-		die("Bug detected: Taking over transport requires non-NULL "
+		die("BUG: taking over transport requires non-NULL "
 		    "smart_options field.");
 
 	data = xcalloc(1, sizeof(*data));
diff --git a/wt-status.c b/wt-status.c
index 4ce4e35..311ae7c 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die(_("bug: unhandled unmerged status %x"), stagemask);
+		die(_("BUG: unhandled unmerged status %x"), stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die(_("bug: unhandled diff status %c"), status);
+		die(_("BUG: unhandled diff status %c"), status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 03/17] Avoid translating bug messages
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 01/17] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 02/17] Report bugs consistently Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 04/17] merge-recursive: clarify code in was_tracked() Johannes Schindelin
                     ` (15 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

While working on the patch series that avoids die()ing in recursive
merges, the issue came up that bug reports (i.e. die("BUG: ...")
constructs) should never be translated, as the target audience is the
Git developer community, not necessarily the current user, and hence
a translated message would make it *harder* to address the problem.

So let's stop translating the obvious ones. As it is really, really
outside the purview of this patch series to see whether there are more
die() statements that report bugs and are currently translated, that
task is left for another day and patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 6 +++---
 wt-status.c       | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 9849439..e51f8fc 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -956,7 +956,7 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 			if (!sha_eq(a->sha1, b->sha1))
 				result.clean = 0;
 		} else
-			die(_("BUG: unsupported object type in the tree"));
+			die("BUG: unsupported object type in the tree");
 	}
 
 	return result;
@@ -1794,7 +1794,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("BUG: fatal merge failure, shouldn't happen."));
+		die("BUG: fatal merge failure, shouldn't happen.");
 
 	return clean_merge;
 }
@@ -1852,7 +1852,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("BUG: unprocessed path??? %s"),
+				die("BUG: unprocessed path??? %s",
 				    entries->items[i].string);
 		}
 
diff --git a/wt-status.c b/wt-status.c
index 311ae7c..75c1162 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die(_("BUG: unhandled unmerged status %x"), stagemask);
+		die("BUG: unhandled unmerged status %x", stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die(_("BUG: unhandled diff status %c"), status);
+		die("BUG: unhandled diff status %c", status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 04/17] merge-recursive: clarify code in was_tracked()
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (2 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 03/17] Avoid translating bug messages Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 05/17] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
                     ` (14 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

It can be puzzling to see that was_tracked() asks to get an index entry
by name, but does not take a negative return value for an answer.

The reason we have to do this is that cache_name_pos() only looks for
entries in stage 0, even if nobody asked for any stage in particular.

Let's rewrite the logic a little bit, to handle the easy case early: if
cache_name_pos() returned a non-negative position, we know it is a match,
and we do not even have to compare the name again (cache_name_pos() did
that for us already). We can say right away: yes, this file was tracked.

Only if there was no exact match do we need to look harder for any
matching entry in stage 2.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index e51f8fc..66ce27c 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -658,23 +658,22 @@ static int was_tracked(const char *path)
 {
 	int pos = cache_name_pos(path, strlen(path));
 
-	if (pos < 0)
-		pos = -1 - pos;
-	while (pos < active_nr &&
-	       !strcmp(path, active_cache[pos]->name)) {
+	if (pos >= 0)
+		return pos < active_nr;
+	/*
+	 * cache_name_pos() looks for stage == 0, even if we did not ask for
+	 * it. Let's look for stage == 2 now.
+	 */
+	for (pos = -1 - pos; pos < active_nr &&
+	     !strcmp(path, active_cache[pos]->name); pos++)
 		/*
 		 * If stage #0, it is definitely tracked.
 		 * If it has stage #2 then it was tracked
 		 * before this merge started.  All other
 		 * cases the path was not tracked.
 		 */
-		switch (ce_stage(active_cache[pos])) {
-		case 0:
-		case 2:
+		if (ce_stage(active_cache[pos]) == 2)
 			return 1;
-		}
-		pos++;
-	}
 	return 0;
 }
 
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 05/17] Prepare the builtins for a libified merge_recursive()
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (3 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 04/17] merge-recursive: clarify code in was_tracked() Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 06/17] merge_recursive: abort properly upon errors Johannes Schindelin
                     ` (13 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

Previously, callers of merge_trees() or merge_recursive() expected that
code to die() with an error message. This used to be okay because we
called those commands from scripts, and had a chance to print out a
message in case the command failed fatally (read: with exit code 128).

As scripting incurs its own set of problems (portability, speed,
idiosynchracies of different shells, limited data structures leading to
inefficient code), we are converting more and more of these scripts into
builtins, using library functions directly.

We already tried to use merge_recursive() directly in the builtin
git-am, for example. Unfortunately, we had to roll it back temporarily
because some of the code in merge-recursive.c still deemed it okay to
call die(), when the builtin am code really wanted to print out a useful
advice after the merge failed fatally. In the next commits, we want to
fix that.

The code touched by this commit expected merge_trees() to die() with
some useful message when there is an error condition, but merge_trees()
is going to be improved by converting all die() calls to return error()
instead (i.e. return value -1 after printing out the message as before),
so that the caller can react more flexibly.

This is a step to prepare for the version of merge_trees() that no
longer dies,  even if we just imitate the previous behavior by calling
exit(128): this is what callers of e.g. `git merge` have come to expect.

Note that the callers of the sequencer (revert and cherry-pick) already
fail fast even for the return value -1; The only difference is that they
now get a chance to say "<command> failed".

A caller of merge_trees() might want handle error messages themselves
(or even suppress them). As this patch is already complex enough, we
leave that change for a later patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 4 +++-
 builtin/merge.c    | 2 ++
 sequencer.c        | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index c3486bd..14312f7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -567,8 +567,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.ancestor = old->name;
 			o.branch1 = new->name;
 			o.branch2 = "local";
-			merge_trees(&o, new->commit->tree, work,
+			ret = merge_trees(&o, new->commit->tree, work,
 				old->commit->tree, &result);
+			if (ret < 0)
+				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
 			if (ret)
diff --git a/builtin/merge.c b/builtin/merge.c
index b555a1b..7b898db 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -682,6 +682,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 		hold_locked_index(&lock, 1);
 		clean = merge_recursive(&o, head,
 				remoteheads->item, reversed, &result);
+		if (clean < 0)
+			exit(128);
 		if (active_cache_changed &&
 		    write_locked_index(&the_index, &lock, COMMIT_LOCK))
 			die (_("unable to write %s"), get_index_file());
diff --git a/sequencer.c b/sequencer.c
index c6362d6..13b794a 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	if (clean < 0)
+		return clean;
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
@@ -561,6 +563,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
 					 head, &msgbuf, opts);
+		if (res < 0)
+			return res;
 		write_message(&msgbuf, git_path_merge_msg());
 	} else {
 		struct commit_list *common = NULL;
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 06/17] merge_recursive: abort properly upon errors
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (4 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 05/17] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 07/17] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
                     ` (12 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

There are a couple of places where return values indicating errors
are ignored. Let's teach them manners.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 66ce27c..716488b 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1942,8 +1942,9 @@ int merge_recursive(struct merge_options *o,
 		saved_b2 = o->branch2;
 		o->branch1 = "Temporary merge branch 1";
 		o->branch2 = "Temporary merge branch 2";
-		merge_recursive(o, merged_common_ancestors, iter->item,
-				NULL, &merged_common_ancestors);
+		if (merge_recursive(o, merged_common_ancestors, iter->item,
+				NULL, &merged_common_ancestors) < 0)
+			return -1;
 		o->branch1 = saved_b1;
 		o->branch2 = saved_b2;
 		o->call_depth--;
@@ -1959,6 +1960,8 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	if (clean < 0)
+		return clean;
 
 	if (o->call_depth) {
 		*result = make_virtual_commit(mrtree, "merged tree");
@@ -2015,6 +2018,9 @@ int merge_recursive_generic(struct merge_options *o,
 	hold_locked_index(lock, 1);
 	clean = merge_recursive(o, head_commit, next_commit, ca,
 			result);
+	if (clean < 0)
+		return clean;
+
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
 		return error(_("Unable to write index."));
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 07/17] merge-recursive: avoid returning a wholesale struct
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (5 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 06/17] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 08/17] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
                     ` (11 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

It is technically allowed, as per C89, for functions' return type to
be complete structs (i.e. *not* just pointers to structs).

However, it was just an oversight of this developer when converting
Python code to C code in 6d297f8 (Status update on merge-recursive in
C, 2006-07-08) which introduced such a return type.

Besides, by converting this construct to pass in the struct, we can now
start returning a value that can indicate errors in future patches. This
will help the current effort to libify merge-recursive.c.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 93 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 50 insertions(+), 43 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 716488b..3e3667f 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -886,47 +886,47 @@ static int merge_3way(struct merge_options *o,
 	return merge_status;
 }
 
-static struct merge_file_info merge_file_1(struct merge_options *o,
+static int merge_file_1(struct merge_options *o,
 					   const struct diff_filespec *one,
 					   const struct diff_filespec *a,
 					   const struct diff_filespec *b,
 					   const char *branch1,
-					   const char *branch2)
+					   const char *branch2,
+					   struct merge_file_info *result)
 {
-	struct merge_file_info result;
-	result.merge = 0;
-	result.clean = 1;
+	result->merge = 0;
+	result->clean = 1;
 
 	if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-		result.clean = 0;
+		result->clean = 0;
 		if (S_ISREG(a->mode)) {
-			result.mode = a->mode;
-			hashcpy(result.sha, a->sha1);
+			result->mode = a->mode;
+			hashcpy(result->sha, a->sha1);
 		} else {
-			result.mode = b->mode;
-			hashcpy(result.sha, b->sha1);
+			result->mode = b->mode;
+			hashcpy(result->sha, b->sha1);
 		}
 	} else {
 		if (!sha_eq(a->sha1, one->sha1) && !sha_eq(b->sha1, one->sha1))
-			result.merge = 1;
+			result->merge = 1;
 
 		/*
 		 * Merge modes
 		 */
 		if (a->mode == b->mode || a->mode == one->mode)
-			result.mode = b->mode;
+			result->mode = b->mode;
 		else {
-			result.mode = a->mode;
+			result->mode = a->mode;
 			if (b->mode != one->mode) {
-				result.clean = 0;
-				result.merge = 1;
+				result->clean = 0;
+				result->merge = 1;
 			}
 		}
 
 		if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, one->sha1))
-			hashcpy(result.sha, b->sha1);
+			hashcpy(result->sha, b->sha1);
 		else if (sha_eq(b->sha1, one->sha1))
-			hashcpy(result.sha, a->sha1);
+			hashcpy(result->sha, a->sha1);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
 			int merge_status;
@@ -938,62 +938,63 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 				die(_("Failed to execute internal merge"));
 
 			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result.sha))
+					    blob_type, result->sha))
 				die(_("Unable to add %s to database"),
 				    a->path);
 
 			free(result_buf.ptr);
-			result.clean = (merge_status == 0);
+			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
-			result.clean = merge_submodule(result.sha,
+			result->clean = merge_submodule(result->sha,
 						       one->path, one->sha1,
 						       a->sha1, b->sha1,
 						       !o->call_depth);
 		} else if (S_ISLNK(a->mode)) {
-			hashcpy(result.sha, a->sha1);
+			hashcpy(result->sha, a->sha1);
 
 			if (!sha_eq(a->sha1, b->sha1))
-				result.clean = 0;
+				result->clean = 0;
 		} else
 			die("BUG: unsupported object type in the tree");
 	}
 
-	return result;
+	return 0;
 }
 
-static struct merge_file_info
-merge_file_special_markers(struct merge_options *o,
+static int merge_file_special_markers(struct merge_options *o,
 			   const struct diff_filespec *one,
 			   const struct diff_filespec *a,
 			   const struct diff_filespec *b,
 			   const char *branch1,
 			   const char *filename1,
 			   const char *branch2,
-			   const char *filename2)
+			   const char *filename2,
+			   struct merge_file_info *mfi)
 {
 	char *side1 = NULL;
 	char *side2 = NULL;
-	struct merge_file_info mfi;
+	int ret;
 
 	if (filename1)
 		side1 = xstrfmt("%s:%s", branch1, filename1);
 	if (filename2)
 		side2 = xstrfmt("%s:%s", branch2, filename2);
 
-	mfi = merge_file_1(o, one, a, b,
-			   side1 ? side1 : branch1, side2 ? side2 : branch2);
+	ret = merge_file_1(o, one, a, b,
+		side1 ? side1 : branch1, side2 ? side2 : branch2, mfi);
 	free(side1);
 	free(side2);
-	return mfi;
+	return ret;
 }
 
-static struct merge_file_info merge_file_one(struct merge_options *o,
+static int merge_file_one(struct merge_options *o,
 					 const char *path,
 					 const unsigned char *o_sha, int o_mode,
 					 const unsigned char *a_sha, int a_mode,
 					 const unsigned char *b_sha, int b_mode,
 					 const char *branch1,
-					 const char *branch2)
+					 const char *branch2,
+					 struct merge_file_info *mfi)
 {
 	struct diff_filespec one, a, b;
 
@@ -1004,7 +1005,7 @@ static struct merge_file_info merge_file_one(struct merge_options *o,
 	a.mode = a_mode;
 	hashcpy(b.sha1, b_sha);
 	b.mode = b_mode;
-	return merge_file_1(o, &one, &a, &b, branch1, branch2);
+	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
 static void handle_change_delete(struct merge_options *o,
@@ -1177,11 +1178,14 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		struct merge_file_info mfi;
 		struct diff_filespec other;
 		struct diff_filespec *add;
-		mfi = merge_file_one(o, one->path,
+
+		if (merge_file_one(o, one->path,
 				 one->sha1, one->mode,
 				 a->sha1, a->mode,
 				 b->sha1, b->mode,
-				 ci->branch1, ci->branch2);
+				 ci->branch1, ci->branch2, &mfi))
+			return;
+
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
@@ -1235,12 +1239,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
-	mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
 					    o->branch1, c1->path,
-					    o->branch2, ci->ren1_other.path);
-	mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+					    o->branch2, ci->ren1_other.path, &mfi_c1) ||
+	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 					    o->branch1, ci->ren2_other.path,
-					    o->branch2, c2->path);
+					    o->branch2, c2->path, &mfi_c2))
+		return;
 
 	if (o->call_depth) {
 		/*
@@ -1461,10 +1466,11 @@ static int process_renames(struct merge_options *o,
 				       ren1_dst, branch2);
 				if (o->call_depth) {
 					struct merge_file_info mfi;
-					mfi = merge_file_one(o, ren1_dst, null_sha1, 0,
+					if (merge_file_one(o, ren1_dst, null_sha1, 0,
 							 ren1->pair->two->sha1, ren1->pair->two->mode,
 							 dst_other.sha1, dst_other.mode,
-							 branch1, branch2);
+							 branch1, branch2, &mfi))
+						return -1;
 					output(o, 1, _("Adding merged %s"), ren1_dst);
 					update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
 					try_merge = 0;
@@ -1620,9 +1626,10 @@ static int merge_content(struct merge_options *o,
 		if (dir_in_way(path, !o->call_depth))
 			df_conflict_remains = 1;
 	}
-	mfi = merge_file_special_markers(o, &one, &a, &b,
+	if (merge_file_special_markers(o, &one, &a, &b,
 					 o->branch1, path1,
-					 o->branch2, path2);
+					 o->branch2, path2, &mfi))
+		return -1;
 
 	if (mfi.clean && !df_conflict_remains &&
 	    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 08/17] merge-recursive: allow write_tree_from_memory() to error out
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (6 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 07/17] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 09/17] merge-recursive: handle return values indicating errors Johannes Schindelin
                     ` (10 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

It is possible that a tree cannot be written (think: disk full). We
will want to give the caller a chance to clean up instead of letting
the program die() in such a case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 3e3667f..99f4202 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1873,8 +1873,8 @@ int merge_trees(struct merge_options *o,
 	else
 		clean = 1;
 
-	if (o->call_depth)
-		*result = write_tree_from_memory(o);
+	if (o->call_depth && !(*result = write_tree_from_memory(o)))
+		return -1;
 
 	return clean;
 }
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 09/17] merge-recursive: handle return values indicating errors
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (7 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 08/17] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 10/17] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
                     ` (9 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

We are about to libify the recursive merge machinery, where we only
die() in case of a bug or memory contention. To that end, we must heed
negative return values as indicating errors.

This requires our functions to be careful to pass through error
conditions in call chains, and for quite a few functions this means
that they have to return values to begin with.

The next step will be to convert the places where we currently die() to
return negative values (read: -1) instead.

Note that we ignore errors reported by make_room_for_path(), consistent
with the previous behavior (update_file_flags() used the return value of
make_room_for_path() only to indicate an early return, but not a fatal
error): if the error is really a fatal error, we will notice later; If
not, it was not that serious a problem to begin with. (Witnesses in
favor of this reasoning are t4151-am-abort and t7610-mergetool, which
would start failing if we stopped on errors reported by
make_room_for_path()).

Note: while this patch makes the code slightly less readable in
update_file_flags() (we introduce a new "goto free_buf;" instead of
an explicit "free(buf); return;"), it is a preparatory change for
the next patch where we will convert all of the die() calls in the same
function to go through the free_buf return path instead.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 196 +++++++++++++++++++++++++++++++++---------------------
 1 file changed, 121 insertions(+), 75 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 99f4202..209427c 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -734,7 +734,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	return error(msg, path, _(": perhaps a D/F conflict?"));
 }
 
-static void update_file_flags(struct merge_options *o,
+static int update_file_flags(struct merge_options *o,
 			      const unsigned char *sha,
 			      unsigned mode,
 			      const char *path,
@@ -775,8 +775,7 @@ static void update_file_flags(struct merge_options *o,
 
 		if (make_room_for_path(o, path) < 0) {
 			update_wd = 0;
-			free(buf);
-			goto update_index;
+			goto free_buf;
 		}
 		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
 			int fd;
@@ -799,20 +798,22 @@ static void update_file_flags(struct merge_options *o,
 		} else
 			die(_("do not know what to do with %06o %s '%s'"),
 			    mode, sha1_to_hex(sha), path);
+ free_buf:
 		free(buf);
 	}
  update_index:
 	if (update_cache)
 		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+	return 0;
 }
 
-static void update_file(struct merge_options *o,
+static int update_file(struct merge_options *o,
 			int clean,
 			const unsigned char *sha,
 			unsigned mode,
 			const char *path)
 {
-	update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth);
+	return update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth);
 }
 
 /* Low level file merging, update and removal */
@@ -1008,7 +1009,7 @@ static int merge_file_one(struct merge_options *o,
 	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
-static void handle_change_delete(struct merge_options *o,
+static int handle_change_delete(struct merge_options *o,
 				 const char *path,
 				 const unsigned char *o_sha, int o_mode,
 				 const unsigned char *a_sha, int a_mode,
@@ -1016,6 +1017,7 @@ static void handle_change_delete(struct merge_options *o,
 				 const char *change, const char *change_past)
 {
 	char *renamed = NULL;
+	int ret = 0;
 	if (dir_in_way(path, !o->call_depth)) {
 		renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
 	}
@@ -1026,21 +1028,23 @@ static void handle_change_delete(struct merge_options *o,
 		 * correct; since there is no true "middle point" between
 		 * them, simply reuse the base version for virtual merge base.
 		 */
-		remove_file_from_cache(path);
-		update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
+		ret = remove_file_from_cache(path);
+		if (!ret)
+			ret = update_file(o, 0, o_sha, o_mode,
+					  renamed ? renamed : path);
 	} else if (!a_sha) {
 		if (!renamed) {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path);
-			update_file(o, 0, b_sha, b_mode, path);
+			ret = update_file(o, 0, b_sha, b_mode, path);
 		} else {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path, renamed);
-			update_file(o, 0, b_sha, b_mode, renamed);
+			ret = update_file(o, 0, b_sha, b_mode, renamed);
 		}
 	} else {
 		if (!renamed) {
@@ -1053,7 +1057,7 @@ static void handle_change_delete(struct merge_options *o,
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch2, change_past,
 			       o->branch1, o->branch1, path, renamed);
-			update_file(o, 0, a_sha, a_mode, renamed);
+			ret = update_file(o, 0, a_sha, a_mode, renamed);
 		}
 		/*
 		 * No need to call update_file() on path when !renamed, since
@@ -1063,9 +1067,11 @@ static void handle_change_delete(struct merge_options *o,
 		 */
 	}
 	free(renamed);
+
+	return ret;
 }
 
-static void conflict_rename_delete(struct merge_options *o,
+static int conflict_rename_delete(struct merge_options *o,
 				   struct diff_filepair *pair,
 				   const char *rename_branch,
 				   const char *other_branch)
@@ -1085,21 +1091,19 @@ static void conflict_rename_delete(struct merge_options *o,
 		b_mode = dest->mode;
 	}
 
-	handle_change_delete(o,
+	if (handle_change_delete(o,
 			     o->call_depth ? orig->path : dest->path,
 			     orig->sha1, orig->mode,
 			     a_sha, a_mode,
 			     b_sha, b_mode,
-			     _("rename"), _("renamed"));
-
-	if (o->call_depth) {
-		remove_file_from_cache(dest->path);
-	} else {
-		update_stages(dest->path, NULL,
+			     _("rename"), _("renamed")))
+		return -1;
+	if (o->call_depth)
+		return remove_file_from_cache(dest->path);
+	else
+		return update_stages(dest->path, NULL,
 			      rename_branch == o->branch1 ? dest : NULL,
 			      rename_branch == o->branch1 ? NULL : dest);
-	}
-
 }
 
 static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
@@ -1115,7 +1119,7 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 	return target;
 }
 
-static void handle_file(struct merge_options *o,
+static int handle_file(struct merge_options *o,
 			struct diff_filespec *rename,
 			int stage,
 			struct rename_conflict_info *ci)
@@ -1125,6 +1129,7 @@ static void handle_file(struct merge_options *o,
 	const char *cur_branch, *other_branch;
 	struct diff_filespec other;
 	struct diff_filespec *add;
+	int ret;
 
 	if (stage == 2) {
 		dst_entry = ci->dst_entry1;
@@ -1139,7 +1144,8 @@ static void handle_file(struct merge_options *o,
 	add = filespec_from_entry(&other, dst_entry, stage ^ 1);
 	if (add) {
 		char *add_name = unique_path(o, rename->path, other_branch);
-		update_file(o, 0, add->sha1, add->mode, add_name);
+		if (update_file(o, 0, add->sha1, add->mode, add_name))
+			return -1;
 
 		remove_file(o, 0, rename->path, 0);
 		dst_name = unique_path(o, rename->path, cur_branch);
@@ -1150,17 +1156,20 @@ static void handle_file(struct merge_options *o,
 			       rename->path, other_branch, dst_name);
 		}
 	}
-	update_file(o, 0, rename->sha1, rename->mode, dst_name);
-	if (stage == 2)
-		update_stages(rename->path, NULL, rename, add);
+	if ((ret = update_file(o, 0, rename->sha1, rename->mode, dst_name)))
+		; /* fall through, do allow dst_name to be released */
+	else if (stage == 2)
+		ret = update_stages(rename->path, NULL, rename, add);
 	else
-		update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
+
+	return ret;
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
+static int conflict_rename_rename_1to2(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* One file was renamed in both branches, but to different names. */
@@ -1184,7 +1193,7 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 				 a->sha1, a->mode,
 				 b->sha1, b->mode,
 				 ci->branch1, ci->branch2, &mfi))
-			return;
+			return -1;
 
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
@@ -1192,7 +1201,8 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * pathname and then either rename the add-source file to that
 		 * unique path, or use that unique path instead of src here.
 		 */
-		update_file(o, 0, mfi.sha, mfi.mode, one->path);
+		if (update_file(o, 0, mfi.sha, mfi.mode, one->path))
+			return -1;
 
 		/*
 		 * Above, we put the merged content at the merge-base's
@@ -1203,22 +1213,26 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * resolving the conflict at that path in its favor.
 		 */
 		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
-		if (add)
-			update_file(o, 0, add->sha1, add->mode, a->path);
+		if (add) {
+			if (update_file(o, 0, add->sha1, add->mode, a->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(a->path);
 		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
-		if (add)
-			update_file(o, 0, add->sha1, add->mode, b->path);
+		if (add) {
+			if (update_file(o, 0, add->sha1, add->mode, b->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(b->path);
-	} else {
-		handle_file(o, a, 2, ci);
-		handle_file(o, b, 3, ci);
-	}
+	} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
+		return -1;
+
+	return 0;
 }
 
-static void conflict_rename_rename_2to1(struct merge_options *o,
+static int conflict_rename_rename_2to1(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* Two files, a & b, were renamed to the same thing, c. */
@@ -1229,6 +1243,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	char *path = c1->path; /* == c2->path */
 	struct merge_file_info mfi_c1;
 	struct merge_file_info mfi_c2;
+	int ret;
 
 	output(o, 1, _("CONFLICT (rename/rename): "
 	       "Rename %s->%s in %s. "
@@ -1245,7 +1260,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 					    o->branch1, ci->ren2_other.path,
 					    o->branch2, c2->path, &mfi_c2))
-		return;
+		return -1;
 
 	if (o->call_depth) {
 		/*
@@ -1256,19 +1271,25 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 		 * again later for the non-recursive merge.
 		 */
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
-		update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);
+		ret = update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
+		if (!ret)
+			ret = update_file(o, 0, mfi_c2.sha, mfi_c2.mode,
+				b->path);
 	} else {
 		char *new_path1 = unique_path(o, path, ci->branch1);
 		char *new_path2 = unique_path(o, path, ci->branch2);
 		output(o, 1, _("Renaming %s to %s and %s to %s instead"),
 		       a->path, new_path1, b->path, new_path2);
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
-		update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
+		ret = update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
+		if (!ret)
+			ret = update_file(o, 0, mfi_c2.sha, mfi_c2.mode,
+				new_path2);
 		free(new_path2);
 		free(new_path1);
 	}
+
+	return ret;
 }
 
 static int process_renames(struct merge_options *o,
@@ -1451,12 +1472,13 @@ static int process_renames(struct merge_options *o,
 				 * update_file_flags() instead of
 				 * update_file().
 				 */
-				update_file_flags(o,
+				if (update_file_flags(o,
 						  ren1->pair->two->sha1,
 						  ren1->pair->two->mode,
 						  ren1_dst,
 						  1, /* update_cache */
-						  0  /* update_wd    */);
+						  0  /* update_wd    */))
+					clean_merge = -1;
 			} else if (!sha_eq(dst_other.sha1, null_sha1)) {
 				clean_merge = 0;
 				try_merge = 1;
@@ -1469,20 +1491,26 @@ static int process_renames(struct merge_options *o,
 					if (merge_file_one(o, ren1_dst, null_sha1, 0,
 							 ren1->pair->two->sha1, ren1->pair->two->mode,
 							 dst_other.sha1, dst_other.mode,
-							 branch1, branch2, &mfi))
-						return -1;
+							 branch1, branch2, &mfi)) {
+						clean_merge = -1;
+						goto cleanup_and_return;
+					}
 					output(o, 1, _("Adding merged %s"), ren1_dst);
-					update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
+					if (update_file(o, 0, mfi.sha, mfi.mode, ren1_dst))
+						clean_merge = -1;
 					try_merge = 0;
 				} else {
 					char *new_path = unique_path(o, ren1_dst, branch2);
 					output(o, 1, _("Adding as %s instead"), new_path);
-					update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+					if (update_file(o, 0, dst_other.sha1, dst_other.mode, new_path))
+						clean_merge = -1;
 					free(new_path);
 				}
 			} else
 				try_merge = 1;
 
+			if (clean_merge < 0)
+				goto cleanup_and_return;
 			if (try_merge) {
 				struct diff_filespec *one, *a, *b;
 				src_other.path = (char *)ren1_src;
@@ -1509,6 +1537,7 @@ static int process_renames(struct merge_options *o,
 			}
 		}
 	}
+cleanup_and_return:
 	string_list_clear(&a_by_dst, 0);
 	string_list_clear(&b_by_dst, 0);
 
@@ -1571,13 +1600,13 @@ error_return:
 	return ret;
 }
 
-static void handle_modify_delete(struct merge_options *o,
+static int handle_modify_delete(struct merge_options *o,
 				 const char *path,
 				 unsigned char *o_sha, int o_mode,
 				 unsigned char *a_sha, int a_mode,
 				 unsigned char *b_sha, int b_mode)
 {
-	handle_change_delete(o,
+	return handle_change_delete(o,
 			     path,
 			     o_sha, o_mode,
 			     a_sha, a_mode,
@@ -1656,7 +1685,8 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			update_stages(path, &one, &a, &b);
+			if (update_stages(path, &one, &a, &b))
+				return -1;
 	}
 
 	if (df_conflict_remains) {
@@ -1664,30 +1694,33 @@ static int merge_content(struct merge_options *o,
 		if (o->call_depth) {
 			remove_file_from_cache(path);
 		} else {
-			if (!mfi.clean)
-				update_stages(path, &one, &a, &b);
-			else {
+			if (!mfi.clean) {
+				if (update_stages(path, &one, &a, &b))
+					return -1;
+			} else {
 				int file_from_stage2 = was_tracked(path);
 				struct diff_filespec merged;
 				hashcpy(merged.sha1, mfi.sha);
 				merged.mode = mfi.mode;
 
-				update_stages(path, NULL,
+				if (update_stages(path, NULL,
 					      file_from_stage2 ? &merged : NULL,
-					      file_from_stage2 ? NULL : &merged);
+					      file_from_stage2 ? NULL : &merged))
+					return -1;
 			}
 
 		}
 		new_path = unique_path(o, path, rename_conflict_info->branch1);
 		output(o, 1, _("Adding as %s instead"), new_path);
-		update_file(o, 0, mfi.sha, mfi.mode, new_path);
+		if (update_file(o, 0, mfi.sha, mfi.mode, new_path)) {
+			free(new_path);
+			return -1;
+		}
 		free(new_path);
 		mfi.clean = 0;
-	} else {
-		update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
-	}
+	} else if (update_file(o, mfi.clean, mfi.sha, mfi.mode, path))
+		return -1;
 	return mfi.clean;
-
 }
 
 /* Per entry merge function */
@@ -1715,17 +1748,21 @@ static int process_entry(struct merge_options *o,
 			break;
 		case RENAME_DELETE:
 			clean_merge = 0;
-			conflict_rename_delete(o, conflict_info->pair1,
+			if (conflict_rename_delete(o,
+					       conflict_info->pair1,
 					       conflict_info->branch1,
-					       conflict_info->branch2);
+					       conflict_info->branch2))
+				clean_merge = -1;
 			break;
 		case RENAME_ONE_FILE_TO_TWO:
 			clean_merge = 0;
-			conflict_rename_rename_1to2(o, conflict_info);
+			if (conflict_rename_rename_1to2(o, conflict_info))
+				clean_merge = -1;
 			break;
 		case RENAME_TWO_FILES_TO_ONE:
 			clean_merge = 0;
-			conflict_rename_rename_2to1(o, conflict_info);
+			if (conflict_rename_rename_2to1(o, conflict_info))
+				clean_merge = -1;
 			break;
 		default:
 			entry->processed = 0;
@@ -1745,8 +1782,9 @@ static int process_entry(struct merge_options *o,
 		} else {
 			/* Modify/delete; deleted side may have put a directory in the way */
 			clean_merge = 0;
-			handle_modify_delete(o, path, o_sha, o_mode,
-					     a_sha, a_mode, b_sha, b_mode);
+			if (handle_modify_delete(o, path, o_sha, o_mode,
+						 a_sha, a_mode, b_sha, b_mode))
+				clean_merge = -1;
 		}
 	} else if ((!o_sha && a_sha && !b_sha) ||
 		   (!o_sha && !a_sha && b_sha)) {
@@ -1778,14 +1816,16 @@ static int process_entry(struct merge_options *o,
 			output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
 			       "Adding %s as %s"),
 			       conf, path, other_branch, path, new_path);
-			update_file(o, 0, sha, mode, new_path);
-			if (o->call_depth)
+			if (update_file(o, 0, sha, mode, new_path))
+				clean_merge = -1;
+			else if (o->call_depth)
 				remove_file_from_cache(path);
 			free(new_path);
 		} else {
 			output(o, 2, _("Adding %s"), path);
 			/* do not overwrite file if already present */
-			update_file_flags(o, sha, mode, path, 1, !a_sha);
+			if (update_file_flags(o, sha, mode, path, 1, !a_sha))
+				clean_merge = -1;
 		}
 	} else if (a_sha && b_sha) {
 		/* Case C: Added in both (check for same permissions) and */
@@ -1848,12 +1888,18 @@ int merge_trees(struct merge_options *o,
 		re_head  = get_renames(o, head, common, head, merge, entries);
 		re_merge = get_renames(o, merge, common, head, merge, entries);
 		clean = process_renames(o, re_head, re_merge);
+		if (clean < 0)
+			return clean;
 		for (i = entries->nr-1; 0 <= i; i--) {
 			const char *path = entries->items[i].string;
 			struct stage_data *e = entries->items[i].util;
-			if (!e->processed
-				&& !process_entry(o, path, e))
-				clean = 0;
+			if (!e->processed) {
+				int ret = process_entry(o, path, e);
+				if (!ret)
+					clean = 0;
+				else if (ret < 0)
+					return ret;
+			}
 		}
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 10/17] merge-recursive: switch to returning errors instead of dying
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (8 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 09/17] merge-recursive: handle return values indicating errors Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-05 11:23   ` [PATCH v2 11/17] am: counteract gender bias Johannes Schindelin
                     ` (8 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

The recursive merge machinery is supposed to be a library function, i.e.
it should return an error when it fails. Originally the functions were
part of the builtin "merge-recursive", though, where it was simpler to
call die() and be done with error handling.

The existing callers were already prepared to detect negative return
values to indicate errors and to behave as previously: exit with code 128
(which is the same thing that die() does, after printing the message).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 59 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 33 insertions(+), 26 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 209427c..10010a4 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -266,8 +266,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 		active_cache_tree = cache_tree();
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
-	    cache_tree_update(&the_index, 0) < 0)
-		die(_("error building trees"));
+	    cache_tree_update(&the_index, 0) < 0) {
+		error(_("error building trees"));
+		return NULL;
+	}
 
 	result = lookup_tree(active_cache_tree->sha1);
 
@@ -708,12 +710,10 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	/* Make sure leading directories are created */
 	status = safe_create_leading_directories_const(path);
 	if (status) {
-		if (status == SCLD_EXISTS) {
+		if (status == SCLD_EXISTS)
 			/* something else exists */
-			error(msg, path, _(": perhaps a D/F conflict?"));
-			return -1;
-		}
-		die(msg, path, "");
+			return error(msg, path, _(": perhaps a D/F conflict?"));
+		return error(msg, path, "");
 	}
 
 	/*
@@ -741,6 +741,8 @@ static int update_file_flags(struct merge_options *o,
 			      int update_cache,
 			      int update_wd)
 {
+	int ret = 0;
+
 	if (o->call_depth)
 		update_wd = 0;
 
@@ -761,9 +763,11 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(sha, &type, &size);
 		if (!buf)
-			die(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
-		if (type != OBJ_BLOB)
-			die(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
+			return error(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
+		if (type != OBJ_BLOB) {
+			ret = error(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
+			goto free_buf;
+		}
 		if (S_ISREG(mode)) {
 			struct strbuf strbuf = STRBUF_INIT;
 			if (convert_to_working_tree(path, buf, size, &strbuf)) {
@@ -784,8 +788,10 @@ static int update_file_flags(struct merge_options *o,
 			else
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-			if (fd < 0)
-				die_errno(_("failed to open '%s'"), path);
+			if (fd < 0) {
+				ret = error_errno(_("failed to open '%s'"), path);
+				goto free_buf;
+			}
 			write_in_full(fd, buf, size);
 			close(fd);
 		} else if (S_ISLNK(mode)) {
@@ -793,18 +799,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				die_errno(_("failed to symlink '%s'"), path);
+				ret = error_errno(_("failed to symlink '%s'"), path);
 			free(lnk);
 		} else
-			die(_("do not know what to do with %06o %s '%s'"),
-			    mode, sha1_to_hex(sha), path);
+			ret = error(_("do not know what to do with %06o %s '%s'"),
+				mode, sha1_to_hex(sha), path);
  free_buf:
 		free(buf);
 	}
  update_index:
-	if (update_cache)
+	if (!ret && update_cache)
 		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-	return 0;
+	return ret;
 }
 
 static int update_file(struct merge_options *o,
@@ -930,20 +936,22 @@ static int merge_file_1(struct merge_options *o,
 			hashcpy(result->sha, a->sha1);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
-			int merge_status;
+			int ret = 0, merge_status;
 
 			merge_status = merge_3way(o, &result_buf, one, a, b,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				die(_("Failed to execute internal merge"));
+				ret = error(_("Failed to execute internal merge"));
 
-			if (write_sha1_file(result_buf.ptr, result_buf.size,
+			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 					    blob_type, result->sha))
-				die(_("Unable to add %s to database"),
-				    a->path);
+				ret = error(_("Unable to add %s to database"),
+					a->path);
 
 			free(result_buf.ptr);
+			if (ret)
+				return ret;
 			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
 			result->clean = merge_submodule(result->sha,
@@ -1868,11 +1876,10 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			die(_("merging of trees %s and %s failed"),
+			error(_("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
-		else
-			exit(128);
+		return -1;
 	}
 
 	if (unmerged_cache()) {
@@ -2003,7 +2010,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			die(_("merge returned no commit"));
+			return error(_("merge returned no commit"));
 	}
 
 	discard_cache();
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 11/17] am: counteract gender bias
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (9 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 10/17] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
@ 2016-07-05 11:23   ` Johannes Schindelin
  2016-07-06 21:22     ` Junio C Hamano
  2016-07-05 11:24   ` [PATCH v2 12/17] am -3: use merge_recursive() directly again Johannes Schindelin
                     ` (7 subsequent siblings)
  18 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), i.e. for
almost 11 years already, we demonstrated our disrespect to the pioneers
of software development like Ada Lovelace, Grace Hopper and Margaret
Hamilton, by pretending that each and every software developer is male
("his_tree"). It appears almost as if we weren't fully aware that the
first professional software developers were all female.

We know our field to have this unfortunate gender bias that has nothing
to do with qualification or biological reasons, and we are very sad
about the current gender imbalance of the Git developer community.

Let's start changing that by using the variable name "her_tree" for an
equal number of years out of fairness, and change to the gender neutral
"their_tree" after that.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/am.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 3dfe70b..f07f89a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1578,14 +1578,14 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 }
 
 /**
- * Do the three-way merge using fake ancestor, his tree constructed
+ * Do the three-way merge using fake ancestor, her tree constructed
  * from the fake ancestor and the postimage of the patch, and our
  * state.
  */
 static int run_fallback_merge_recursive(const struct am_state *state,
 					unsigned char *orig_tree,
 					unsigned char *our_tree,
-					unsigned char *his_tree)
+					unsigned char *her_tree)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	int status;
@@ -1593,7 +1593,7 @@ static int run_fallback_merge_recursive(const struct am_state *state,
 	cp.git_cmd = 1;
 
 	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
-			 sha1_to_hex(his_tree), linelen(state->msg), state->msg);
+			 sha1_to_hex(her_tree), linelen(state->msg), state->msg);
 	if (state->quiet)
 		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
 
@@ -1601,7 +1601,7 @@ static int run_fallback_merge_recursive(const struct am_state *state,
 	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
 	argv_array_push(&cp.args, "--");
 	argv_array_push(&cp.args, sha1_to_hex(our_tree));
-	argv_array_push(&cp.args, sha1_to_hex(his_tree));
+	argv_array_push(&cp.args, sha1_to_hex(her_tree));
 
 	status = run_command(&cp) ? (-1) : 0;
 	discard_cache();
@@ -1614,7 +1614,7 @@ static int run_fallback_merge_recursive(const struct am_state *state,
  */
 static int fall_back_threeway(const struct am_state *state, const char *index_path)
 {
-	unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
+	unsigned char orig_tree[GIT_SHA1_RAWSZ], her_tree[GIT_SHA1_RAWSZ],
 		      our_tree[GIT_SHA1_RAWSZ];
 
 	if (get_sha1("HEAD", our_tree) < 0)
@@ -1651,7 +1651,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		return error(_("Did you hand edit your patch?\n"
 				"It does not apply to blobs recorded in its index."));
 
-	if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(her_tree, &the_index, index_path, 0, NULL))
 		return error("could not write tree");
 
 	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
@@ -1661,13 +1661,13 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 
 	/*
 	 * This is not so wrong. Depending on which base we picked, orig_tree
-	 * may be wildly different from ours, but his_tree has the same set of
+	 * may be wildly different from ours, but her_tree has the same set of
 	 * wildly different changes in parts the patch did not touch, so
 	 * recursive ends up canceling them, saying that we reverted all those
 	 * changes.
 	 */
 
-	if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) {
+	if (run_fallback_merge_recursive(state, orig_tree, our_tree, her_tree)) {
 		rerere(state->allow_rerere_autoupdate);
 		return error(_("Failed to merge in the changes."));
 	}
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 12/17] am -3: use merge_recursive() directly again
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (10 preceding siblings ...)
  2016-07-05 11:23   ` [PATCH v2 11/17] am: counteract gender bias Johannes Schindelin
@ 2016-07-05 11:24   ` Johannes Schindelin
  2016-07-05 11:24   ` [PATCH v2 13/17] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
                     ` (6 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

Last October, we had to change this code to run `git merge-recursive`
in a child process: git-am wants to print some helpful advice when the
merge failed, but the code in question was not prepared to return, it
die()d instead.

We are finally at a point when the code *is* prepared to return errors,
and can avoid the child process again.

This reverts commit c63d4b2 (am -3: do not let failed merge from
completing the error codepath, 2015-10-09).

Note: the code now calls merge_recursive_generic() again. Unlike
merge_trees() and merge_recursive(), this function returns 0 upon success,
as most of Git's functions. Therefore, the error value -1 naturally is
handled correctly, and we do not have to take care of it specifically.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/am.c | 49 ++++++++++++++++---------------------------------
 1 file changed, 16 insertions(+), 33 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index f07f89a..be652f9 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1578,44 +1578,16 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 }
 
 /**
- * Do the three-way merge using fake ancestor, her tree constructed
- * from the fake ancestor and the postimage of the patch, and our
- * state.
- */
-static int run_fallback_merge_recursive(const struct am_state *state,
-					unsigned char *orig_tree,
-					unsigned char *our_tree,
-					unsigned char *her_tree)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-	int status;
-
-	cp.git_cmd = 1;
-
-	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
-			 sha1_to_hex(her_tree), linelen(state->msg), state->msg);
-	if (state->quiet)
-		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
-
-	argv_array_push(&cp.args, "merge-recursive");
-	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
-	argv_array_push(&cp.args, "--");
-	argv_array_push(&cp.args, sha1_to_hex(our_tree));
-	argv_array_push(&cp.args, sha1_to_hex(her_tree));
-
-	status = run_command(&cp) ? (-1) : 0;
-	discard_cache();
-	read_cache();
-	return status;
-}
-
-/**
  * Attempt a threeway merge, using index_path as the temporary index.
  */
 static int fall_back_threeway(const struct am_state *state, const char *index_path)
 {
 	unsigned char orig_tree[GIT_SHA1_RAWSZ], her_tree[GIT_SHA1_RAWSZ],
 		      our_tree[GIT_SHA1_RAWSZ];
+	const unsigned char *bases[1] = {orig_tree};
+	struct merge_options o;
+	struct commit *result;
+	char *her_tree_name;
 
 	if (get_sha1("HEAD", our_tree) < 0)
 		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
@@ -1667,11 +1639,22 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	 * changes.
 	 */
 
-	if (run_fallback_merge_recursive(state, orig_tree, our_tree, her_tree)) {
+	init_merge_options(&o);
+
+	o.branch1 = "HEAD";
+	her_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+	o.branch2 = her_tree_name;
+
+	if (state->quiet)
+		o.verbosity = 0;
+
+	if (merge_recursive_generic(&o, our_tree, her_tree, 1, bases, &result)) {
 		rerere(state->allow_rerere_autoupdate);
+		free(her_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
 
+	free(her_tree_name);
 	return 0;
 }
 
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 13/17] merge-recursive: flush output buffer before printing error messages
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (11 preceding siblings ...)
  2016-07-05 11:24   ` [PATCH v2 12/17] am -3: use merge_recursive() directly again Johannes Schindelin
@ 2016-07-05 11:24   ` Johannes Schindelin
  2016-07-05 11:24   ` [PATCH v2 14/17] merge-recursive: write the commit title in one go Johannes Schindelin
                     ` (5 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

The data structure passed to the recursive merge machinery has a feature
where the caller can ask for the output to be buffered into a strbuf, by
setting the field 'buffer_output'.

Previously, we simply swallowed the buffered output when showing error
messages. With this patch, we show the output first, and only then print
the error message.

Currently, the only user of that buffering is merge_recursive() itself,
to avoid the progress output to interfere.

In the next patches, we will introduce a new buffer_output mode that
forces merge_recursive() to retain the output buffer for further
processing by the caller. If the caller asked for that, we will then
also write the error messages into the output buffer. This is necessary
to give the caller more control not only how to react in case of errors
but also control how/if to display the error messages.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 110 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 65 insertions(+), 45 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 10010a4..0eb23a6 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -23,6 +23,28 @@
 #include "dir.h"
 #include "submodule.h"
 
+static void flush_output(struct merge_options *o)
+{
+	if (o->obuf.len) {
+		fputs(o->obuf.buf, stdout);
+		strbuf_reset(&o->obuf);
+	}
+}
+
+static int err(struct merge_options *o, const char *err, ...)
+{
+	va_list params;
+
+	va_start(params, err);
+	flush_output(o);
+	strbuf_vaddf(&o->obuf, err, params);
+	error("%s", o->obuf.buf);
+	strbuf_reset(&o->obuf);
+	va_end(params);
+
+	return -1;
+}
+
 static struct tree *shift_tree_object(struct tree *one, struct tree *two,
 				      const char *subtree_shift)
 {
@@ -148,14 +170,6 @@ static int show(struct merge_options *o, int v)
 	return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
 }
 
-static void flush_output(struct merge_options *o)
-{
-	if (o->obuf.len) {
-		fputs(o->obuf.buf, stdout);
-		strbuf_reset(&o->obuf);
-	}
-}
-
 __attribute__((format (printf, 3, 4)))
 static void output(struct merge_options *o, int v, const char *fmt, ...)
 {
@@ -198,7 +212,8 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
 	}
 }
 
-static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+static int add_cacheinfo(struct merge_options *o,
+		unsigned int mode, const unsigned char *sha1,
 		const char *path, int stage, int refresh, int options)
 {
 	struct cache_entry *ce;
@@ -206,7 +221,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
 			      (refresh ? (CE_MATCH_REFRESH |
 					  CE_MATCH_IGNORE_MISSING) : 0 ));
 	if (!ce)
-		return error(_("addinfo_cache failed for path '%s'"), path);
+		return err(o, _("addinfo_cache failed for path '%s'"), path);
 	return add_cache_entry(ce, options);
 }
 
@@ -267,7 +282,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
 	    cache_tree_update(&the_index, 0) < 0) {
-		error(_("error building trees"));
+		err(o, _("error building trees"));
 		return NULL;
 	}
 
@@ -535,7 +550,8 @@ static struct string_list *get_renames(struct merge_options *o,
 	return renames;
 }
 
-static int update_stages(const char *path, const struct diff_filespec *o,
+static int update_stages(struct merge_options *opt, const char *path,
+			 const struct diff_filespec *o,
 			 const struct diff_filespec *a,
 			 const struct diff_filespec *b)
 {
@@ -554,13 +570,13 @@ static int update_stages(const char *path, const struct diff_filespec *o,
 		if (remove_file_from_cache(path))
 			return -1;
 	if (o)
-		if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
+		if (add_cacheinfo(opt, o->mode, o->sha1, path, 1, 0, options))
 			return -1;
 	if (a)
-		if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
+		if (add_cacheinfo(opt, a->mode, a->sha1, path, 2, 0, options))
 			return -1;
 	if (b)
-		if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
+		if (add_cacheinfo(opt, b->mode, b->sha1, path, 3, 0, options))
 			return -1;
 	return 0;
 }
@@ -712,8 +728,8 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	if (status) {
 		if (status == SCLD_EXISTS)
 			/* something else exists */
-			return error(msg, path, _(": perhaps a D/F conflict?"));
-		return error(msg, path, "");
+			return err(o, msg, path, _(": perhaps a D/F conflict?"));
+		return err(o, msg, path, "");
 	}
 
 	/*
@@ -721,7 +737,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	 * tracking it.
 	 */
 	if (would_lose_untracked(path))
-		return error(_("refusing to lose untracked file at '%s'"),
+		return err(o, _("refusing to lose untracked file at '%s'"),
 			     path);
 
 	/* Successful unlink is good.. */
@@ -731,7 +747,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	if (errno == ENOENT)
 		return 0;
 	/* .. but not some other error (who really cares what?) */
-	return error(msg, path, _(": perhaps a D/F conflict?"));
+	return err(o, msg, path, _(": perhaps a D/F conflict?"));
 }
 
 static int update_file_flags(struct merge_options *o,
@@ -763,9 +779,9 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(sha, &type, &size);
 		if (!buf)
-			return error(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
+			return err(o, _("cannot read object %s '%s'"), sha1_to_hex(sha), path);
 		if (type != OBJ_BLOB) {
-			ret = error(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
+			ret = err(o, _("blob expected for %s '%s'"), sha1_to_hex(sha), path);
 			goto free_buf;
 		}
 		if (S_ISREG(mode)) {
@@ -789,7 +805,8 @@ static int update_file_flags(struct merge_options *o,
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
 			if (fd < 0) {
-				ret = error_errno(_("failed to open '%s'"), path);
+				ret = err(o, _("failed to open '%s': %s"),
+					path, strerror(errno));
 				goto free_buf;
 			}
 			write_in_full(fd, buf, size);
@@ -799,17 +816,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				ret = error_errno(_("failed to symlink '%s'"), path);
+				ret = err(o, _("failed to symlink '%s': %s"),
+					path, strerror(errno));
 			free(lnk);
 		} else
-			ret = error(_("do not know what to do with %06o %s '%s'"),
+			ret = err(o, _("do not know what to do with %06o %s '%s'"),
 				mode, sha1_to_hex(sha), path);
  free_buf:
 		free(buf);
 	}
  update_index:
 	if (!ret && update_cache)
-		add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+		add_cacheinfo(o, mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 	return ret;
 }
 
@@ -942,11 +960,11 @@ static int merge_file_1(struct merge_options *o,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				ret = error(_("Failed to execute internal merge"));
+				ret = err(o, _("Failed to execute internal merge"));
 
 			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 					    blob_type, result->sha))
-				ret = error(_("Unable to add %s to database"),
+				ret = err(o, _("Unable to add %s to database"),
 					a->path);
 
 			free(result_buf.ptr);
@@ -1109,7 +1127,7 @@ static int conflict_rename_delete(struct merge_options *o,
 	if (o->call_depth)
 		return remove_file_from_cache(dest->path);
 	else
-		return update_stages(dest->path, NULL,
+		return update_stages(o, dest->path, NULL,
 			      rename_branch == o->branch1 ? dest : NULL,
 			      rename_branch == o->branch1 ? NULL : dest);
 }
@@ -1167,9 +1185,9 @@ static int handle_file(struct merge_options *o,
 	if ((ret = update_file(o, 0, rename->sha1, rename->mode, dst_name)))
 		; /* fall through, do allow dst_name to be released */
 	else if (stage == 2)
-		ret = update_stages(rename->path, NULL, rename, add);
+		ret = update_stages(o, rename->path, NULL, rename, add);
 	else
-		ret = update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(o, rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
@@ -1557,23 +1575,25 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
 	return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
 }
 
-static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
+static int read_sha1_strbuf(struct merge_options *o,
+	const unsigned char *sha1, struct strbuf *dst)
 {
 	void *buf;
 	enum object_type type;
 	unsigned long size;
 	buf = read_sha1_file(sha1, &type, &size);
 	if (!buf)
-		return error(_("cannot read object %s"), sha1_to_hex(sha1));
+		return err(o, _("cannot read object %s"), sha1_to_hex(sha1));
 	if (type != OBJ_BLOB) {
 		free(buf);
-		return error(_("object %s is not a blob"), sha1_to_hex(sha1));
+		return err(o, _("object %s is not a blob"), sha1_to_hex(sha1));
 	}
 	strbuf_attach(dst, buf, size, size + 1);
 	return 0;
 }
 
-static int blob_unchanged(const unsigned char *o_sha,
+static int blob_unchanged(struct merge_options *opt,
+			  const unsigned char *o_sha,
 			  unsigned o_mode,
 			  const unsigned char *a_sha,
 			  unsigned a_mode,
@@ -1591,7 +1611,7 @@ static int blob_unchanged(const unsigned char *o_sha,
 		return 0;
 
 	assert(o_sha && a_sha);
-	if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
+	if (read_sha1_strbuf(opt, o_sha, &o) || read_sha1_strbuf(opt, a_sha, &a))
 		goto error_return;
 	/*
 	 * Note: binary | is used so that both renormalizations are
@@ -1680,7 +1700,7 @@ static int merge_content(struct merge_options *o,
 		 */
 		path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
 		if (!path_renamed_outside_HEAD) {
-			add_cacheinfo(mfi.mode, mfi.sha, path,
+			add_cacheinfo(o, mfi.mode, mfi.sha, path,
 				      0, (!o->call_depth), 0);
 			return mfi.clean;
 		}
@@ -1693,7 +1713,7 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			if (update_stages(path, &one, &a, &b))
+			if (update_stages(o, path, &one, &a, &b))
 				return -1;
 	}
 
@@ -1703,7 +1723,7 @@ static int merge_content(struct merge_options *o,
 			remove_file_from_cache(path);
 		} else {
 			if (!mfi.clean) {
-				if (update_stages(path, &one, &a, &b))
+				if (update_stages(o, path, &one, &a, &b))
 					return -1;
 			} else {
 				int file_from_stage2 = was_tracked(path);
@@ -1711,7 +1731,7 @@ static int merge_content(struct merge_options *o,
 				hashcpy(merged.sha1, mfi.sha);
 				merged.mode = mfi.mode;
 
-				if (update_stages(path, NULL,
+				if (update_stages(o, path, NULL,
 					      file_from_stage2 ? &merged : NULL,
 					      file_from_stage2 ? NULL : &merged))
 					return -1;
@@ -1779,8 +1799,8 @@ static int process_entry(struct merge_options *o,
 	} else if (o_sha && (!a_sha || !b_sha)) {
 		/* Case A: Deleted in one */
 		if ((!a_sha && !b_sha) ||
-		    (!b_sha && blob_unchanged(o_sha, o_mode, a_sha, a_mode, normalize, path)) ||
-		    (!a_sha && blob_unchanged(o_sha, o_mode, b_sha, b_mode, normalize, path))) {
+		    (!b_sha && blob_unchanged(o, o_sha, o_mode, a_sha, a_mode, normalize, path)) ||
+		    (!a_sha && blob_unchanged(o, o_sha, o_mode, b_sha, b_mode, normalize, path))) {
 			/* Deleted in both or deleted in one and
 			 * unchanged in the other */
 			if (a_sha)
@@ -1876,7 +1896,7 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			error(_("merging of trees %s and %s failed"),
+			err(o, _("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
 		return -1;
@@ -2010,7 +2030,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			return error(_("merge returned no commit"));
+			return err(o, _("merge returned no commit"));
 	}
 
 	discard_cache();
@@ -2069,7 +2089,7 @@ int merge_recursive_generic(struct merge_options *o,
 		for (i = 0; i < num_base_list; ++i) {
 			struct commit *base;
 			if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))
-				return error(_("Could not parse object '%s'"),
+				return err(o, _("Could not parse object '%s'"),
 					sha1_to_hex(base_list[i]));
 			commit_list_insert(base, &ca);
 		}
@@ -2083,7 +2103,7 @@ int merge_recursive_generic(struct merge_options *o,
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
-		return error(_("Unable to write index."));
+		return err(o, _("Unable to write index."));
 
 	return clean ? 0 : 1;
 }
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 14/17] merge-recursive: write the commit title in one go
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (12 preceding siblings ...)
  2016-07-05 11:24   ` [PATCH v2 13/17] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
@ 2016-07-05 11:24   ` Johannes Schindelin
  2016-07-05 11:24   ` [PATCH v2 15/17] merge-recursive: offer an option to retain the output in 'obuf' Johannes Schindelin
                     ` (4 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

In 66a155b (Enable output buffering in merge-recursive., 2007-01-14), we
changed the code such that it prints the output in one go, to avoid
interfering with the progress output.

Let's make sure that the same holds true when outputting the commit
title: previously, we used several printf() statements to stdout and
speculated that stdout's buffer is large enough to hold the entire
commit title.

Apart from making that speculation unnecessary, we change the code to
add the message to the output buffer before flushing for another reason:
the next commit will introduce a new level of output buffering, where
the caller can request the output not to be flushed, but to be retained
for further processing.

This latter feature will be needed when teaching the sequencer to do
rebase -i's brunt work: it wants to control the output of the
cherry-picks (i.e. recursive merges).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 0eb23a6..81836f2 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -191,25 +191,26 @@ static void output(struct merge_options *o, int v, const char *fmt, ...)
 
 static void output_commit_title(struct merge_options *o, struct commit *commit)
 {
-	int i;
-	flush_output(o);
-	for (i = o->call_depth; i--;)
-		fputs("  ", stdout);
+	strbuf_addchars(&o->obuf, ' ', o->call_depth * 2);
 	if (commit->util)
-		printf("virtual %s\n", merge_remote_util(commit)->name);
+		strbuf_addf(&o->obuf, "virtual %s\n",
+			merge_remote_util(commit)->name);
 	else {
-		printf("%s ", find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
+		strbuf_addf(&o->obuf, "%s ",
+			find_unique_abbrev(commit->object.oid.hash,
+				DEFAULT_ABBREV));
 		if (parse_commit(commit) != 0)
-			printf(_("(bad commit)\n"));
+			strbuf_addf(&o->obuf, _("(bad commit)\n"));
 		else {
 			const char *title;
 			const char *msg = get_commit_buffer(commit, NULL);
 			int len = find_commit_subject(msg, &title);
 			if (len)
-				printf("%.*s\n", len, title);
+				strbuf_addf(&o->obuf, "%.*s\n", len, title);
 			unuse_commit_buffer(commit, msg);
 		}
 	}
+	flush_output(o);
 }
 
 static int add_cacheinfo(struct merge_options *o,
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 15/17] merge-recursive: offer an option to retain the output in 'obuf'
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (13 preceding siblings ...)
  2016-07-05 11:24   ` [PATCH v2 14/17] merge-recursive: write the commit title in one go Johannes Schindelin
@ 2016-07-05 11:24   ` Johannes Schindelin
  2016-07-05 11:24   ` [PATCH v2 16/17] Ensure that the output buffer is released after calling merge_trees() Johannes Schindelin
                     ` (3 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

Since 66a155b (Enable output buffering in merge-recursive., 2007-01-14),
we already accumulate the output in a buffer. The idea was to avoid
interfering with the progress output that goes to stderr, which is
unbuffered, when we write to stdout, which is buffered.

We extend that buffering to allow the caller to handle the output
(possibly suppressing it). This will help us when extending the
sequencer to do rebase -i's brunt work: it does not want the picks to
print anything by default but instead determine itself whether to print
the output or not.

Note that we also redirect the error messages into the output buffer
when the caller asked not to flush the output buffer, for two reasons:
1) to retain the correct output order, and 2) to allow the caller to
suppress *all* output.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 +++++++++++++----
 merge-recursive.h |  2 +-
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 81836f2..29cbdac 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -25,7 +25,7 @@
 
 static void flush_output(struct merge_options *o)
 {
-	if (o->obuf.len) {
+	if (o->buffer_output < 2 && o->obuf.len) {
 		fputs(o->obuf.buf, stdout);
 		strbuf_reset(&o->obuf);
 	}
@@ -36,10 +36,19 @@ static int err(struct merge_options *o, const char *err, ...)
 	va_list params;
 
 	va_start(params, err);
-	flush_output(o);
+	if (o->buffer_output < 2)
+		flush_output(o);
+	else {
+		strbuf_complete(&o->obuf, '\n');
+		strbuf_addstr(&o->obuf, "error: ");
+	}
 	strbuf_vaddf(&o->obuf, err, params);
-	error("%s", o->obuf.buf);
-	strbuf_reset(&o->obuf);
+	if (o->buffer_output > 1)
+		strbuf_addch(&o->obuf, '\n');
+	else {
+		error("%s", o->obuf.buf);
+		strbuf_reset(&o->obuf);
+	}
 	va_end(params);
 
 	return -1;
diff --git a/merge-recursive.h b/merge-recursive.h
index 52f0201..407d4fc 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -13,7 +13,7 @@ struct merge_options {
 		MERGE_RECURSIVE_THEIRS
 	} recursive_variant;
 	const char *subtree_shift;
-	unsigned buffer_output : 1;
+	unsigned buffer_output : 2; /* 1: output at end, 2: keep buffered */
 	unsigned renormalize : 1;
 	long xdl_opts;
 	int verbosity;
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 16/17] Ensure that the output buffer is released after calling merge_trees()
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (14 preceding siblings ...)
  2016-07-05 11:24   ` [PATCH v2 15/17] merge-recursive: offer an option to retain the output in 'obuf' Johannes Schindelin
@ 2016-07-05 11:24   ` Johannes Schindelin
  2016-07-05 11:24   ` [PATCH v2 17/17] merge-recursive: flush output buffer even when erroring out Johannes Schindelin
                     ` (2 subsequent siblings)
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

The recursive merge machinery accumulates its output in an output
buffer, to be flushed at the end of merge_recursive(). At this point,
we forgot to release the output buffer.

When calling merge_trees() (i.e. the non-recursive part of the recursive
merge) directly, the output buffer is never flushed because the caller
may be merge_recursive() which wants to flush the output itself.

For the same reason, merge_trees() cannot release the output buffer: it
may still be needed.

Forgetting to release the output buffer did not matter much when running
git-checkout, or git-merge-recursive, because we exited after the
operation anyway. Ever since cherry-pick learned to pick a commit range,
however, this memory leak had the potential of becoming a problem.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 1 +
 merge-recursive.c  | 2 ++
 sequencer.c        | 1 +
 3 files changed, 4 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 14312f7..ced4ac4 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -573,6 +573,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
+			strbuf_release(&o.obuf);
 			if (ret)
 				return ret;
 		}
diff --git a/merge-recursive.c b/merge-recursive.c
index 29cbdac..fdc624a 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2059,6 +2059,8 @@ int merge_recursive(struct merge_options *o,
 		commit_list_insert(h2, &(*result)->parents->next);
 	}
 	flush_output(o);
+	if (o->buffer_output < 2)
+		strbuf_release(&o->obuf);
 	if (show(o, 2))
 		diff_warn_rename_limit("merge.renamelimit",
 				       o->needed_rename_limit, 0);
diff --git a/sequencer.c b/sequencer.c
index 13b794a..8ceeb1b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	strbuf_release(&o.obuf);
 	if (clean < 0)
 		return clean;
 
-- 
2.9.0.280.g32e2a70



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v2 17/17] merge-recursive: flush output buffer even when erroring out
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (15 preceding siblings ...)
  2016-07-05 11:24   ` [PATCH v2 16/17] Ensure that the output buffer is released after calling merge_trees() Johannes Schindelin
@ 2016-07-05 11:24   ` Johannes Schindelin
  2016-07-06 21:26   ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Junio C Hamano
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
  18 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

Ever since 66a155b (Enable output buffering in merge-recursive.,
2007-01-14), we had a problem: When the merge failed in a fatal way, all
regular output was swallowed because we called die() and did not get a
chance to drain the output buffers.

To fix this, several modifications were necessary:

- we needed to stop die()ing, to give callers a chance to do something
  when an error occurred (in this case, flush the output buffers),

- we needed to delay printing the error message so that the caller can
  print the buffered output before that, and

- we needed to make sure that the output buffers are flushed even when
  the return value indicates an error.

The first two changes were introduced through earlier commits in this
patch series, and this commit addresses the third one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index fdc624a..d94f853 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2050,6 +2050,7 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	flush_output(o);
 	if (clean < 0)
 		return clean;
 
@@ -2058,7 +2059,6 @@ int merge_recursive(struct merge_options *o,
 		commit_list_insert(h1, &(*result)->parents);
 		commit_list_insert(h2, &(*result)->parents->next);
 	}
-	flush_output(o);
 	if (o->buffer_output < 2)
 		strbuf_release(&o->obuf);
 	if (show(o, 2))
-- 
2.9.0.280.g32e2a70

^ permalink raw reply related	[flat|nested] 262+ messages in thread

* Re: [PATCH 1/9] Report bugs consistently
  2016-07-02  8:01       ` Duy Nguyen
@ 2016-07-05 11:32         ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 11:32 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List, Junio C Hamano

Hi Duy,

On Sat, 2 Jul 2016, Duy Nguyen wrote:

> You're changing the string and adding more work to translators. So
> either leave the string untouched, or drop _().

Thanks. I addressed that concern in v2. Could you please now have a look at
the parts of the patch series which could possibly regress Git's
functionality? I am quite a bit more interested in having extra pairs of
eyes look over those.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 02/17] Report bugs consistently
  2016-07-05 11:23   ` [PATCH v2 02/17] Report bugs consistently Johannes Schindelin
@ 2016-07-05 13:05     ` Jakub Narębski
  2016-07-05 13:38       ` Johannes Schindelin
  2016-07-06 15:30     ` Duy Nguyen
  1 sibling, 1 reply; 262+ messages in thread
From: Jakub Narębski @ 2016-07-05 13:05 UTC (permalink / raw)
  To: Johannes Schindelin, git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

W dniu 2016-07-05 o 13:23, Johannes Schindelin pisze:
> diff --git a/builtin/ls-files.c b/builtin/ls-files.c
> index f02e3d2..00ea91a 100644
> --- a/builtin/ls-files.c
> +++ b/builtin/ls-files.c
> @@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
>  				 */
>  				pos = cache_name_pos(ent->name, ent->len);
>  				if (0 <= pos)
> -					die("bug in show-killed-files");
> +					die("BUG: killed-file %.*s not found",
> +						ent->len, ent->name);
>  				pos = -pos - 1;
>  				while (pos < active_nr &&
>  				       ce_stage(active_cache[pos]))

This has an additional improvement (not mentioned in the commit
message, but probably not worth it) in that it shows which file
was not found, not only that there was some bug, isn't it?

-- 
Jakub Narębski


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 02/17] Report bugs consistently
  2016-07-05 13:05     ` Jakub Narębski
@ 2016-07-05 13:38       ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-05 13:38 UTC (permalink / raw)
  To: Jakub Narębski
  Cc: git, Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen

[-- Attachment #1: Type: text/plain, Size: 1256 bytes --]

Hi Kuba,

On Tue, 5 Jul 2016, Jakub Narębski wrote:

> W dniu 2016-07-05 o 13:23, Johannes Schindelin pisze:
> > diff --git a/builtin/ls-files.c b/builtin/ls-files.c
> > index f02e3d2..00ea91a 100644
> > --- a/builtin/ls-files.c
> > +++ b/builtin/ls-files.c
> > @@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
> >  				 */
> >  				pos = cache_name_pos(ent->name, ent->len);
> >  				if (0 <= pos)
> > -					die("bug in show-killed-files");
> > +					die("BUG: killed-file %.*s not found",
> > +						ent->len, ent->name);
> >  				pos = -pos - 1;
> >  				while (pos < active_nr &&
> >  				       ce_stage(active_cache[pos]))
> 
> This has an additional improvement (not mentioned in the commit
> message, but probably not worth it) in that it shows which file
> was not found, not only that there was some bug, isn't it?

Sure, it improves that report. In the unlikely event that a bug is
encountered :-)

Is it really worth mentioning in the commit message?

Looking at it again, however, I think there is a bug in my patch. It says
that the file was not found, but pos was non-negative, so it was found
unexpectedly. So I think I should strike the "not" part. Would you concur?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-07-02  7:20         ` Johannes Schindelin
@ 2016-07-06 15:30           ` Junio C Hamano
  2016-07-07 11:17             ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-06 15:30 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> To understand why we're not done yet, the crucial point is *not* that the
> return value encodes the insert position. The crucial point is that
> despite asking for an index entry matching a specific name, we might not
> find one, *even if there is one*.

I've been wondering why you keep saying "even though we didn't ask,
we look for stage#0", and now I see why.  The cache_pos() interface
*is* about finding the stage#0 entry for the given path.

When it finds none, it indicates where a stage#0 entry of that path
would be inserted, which by the sort-order would give us where
higher stage entries for the path would be found (if there is any).
There is no parameter for you to tell it to find stage#2, and "even
though we didn't ask" is showing (and being the source of) the
confusion.

And I did not want a misleading comment to spread the confusion;
that is why I was reacting strongly.

As you pointed out, we can return early without falling into the
generic "we are still looking at the same path" codepath when we
find thestage#0 entry, so I wouldn't mind doing something like the
following.

static int was_tracked(const char *path)
{
	int pos = cache_name_pos(path, strlen(path));

        if (0 <= pos)
	        /* we have been tracking this path */
        	return 1;

	/*
         * Look for an unmerged entry for the path,
         * specifically stage #2, which would indicate
         * that "our" side before the merge started
         * had the path tracked (and resulted in a conflict).
         */
	for (pos = -1 - pos;
             pos < active_nr && !strcmp(path, active_cache[pos]->name);
	     pos++)
		if (ce_stage(active_cache[pos]) == 2)
			return 1;
	return 0;
}

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 02/17] Report bugs consistently
  2016-07-05 11:23   ` [PATCH v2 02/17] Report bugs consistently Johannes Schindelin
  2016-07-05 13:05     ` Jakub Narębski
@ 2016-07-06 15:30     ` Duy Nguyen
  2016-07-07 11:23       ` Johannes Schindelin
  1 sibling, 1 reply; 262+ messages in thread
From: Duy Nguyen @ 2016-07-06 15:30 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Eric Sunshine, Jeff King,
	Johannes Sixt

On Tue, Jul 5, 2016 at 1:23 PM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> The vast majority of error messages in Git's source code which report a
> bug use the convention to prefix the message with "BUG:".
>
> As part of cleaning up merge-recursive to stop die()ing except in case of
> detected bugs, let's just make the remainder of the bug reports consistent
> with the de facto rule.

If you search 'die(_("bug:' in this patch,  you'll find 5 instances
where _() should be gone too.
-- 
Duy

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 11/17] am: counteract gender bias
  2016-07-05 11:23   ` [PATCH v2 11/17] am: counteract gender bias Johannes Schindelin
@ 2016-07-06 21:22     ` Junio C Hamano
  2016-07-07 11:30       ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-06 21:22 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), i.e. for
> almost 11 years already,...
> ...Let's start changing that by using the variable name "her_tree" for an
> equal number of years out of fairness, and change to the gender neutral
> "their_tree" after that.

I doubt this kind fo distraction is desirable in the middle of a
seriously heavy series like this one.  As a standalone clean-up to
turn these directly to "their" that everybody would agree on and can
be merged down quickly to 'master' that does not have to keep the
body of the main topic waiting for the dust to settle might be a
better approach.

Unless you are trying to discourage the reviewers, that is ;-).

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 00/17] Use merge_recursive() directly in the builtin am
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (16 preceding siblings ...)
  2016-07-05 11:24   ` [PATCH v2 17/17] merge-recursive: flush output buffer even when erroring out Johannes Schindelin
@ 2016-07-06 21:26   ` Junio C Hamano
  2016-07-07 11:16     ` Johannes Schindelin
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
  18 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-06 21:26 UTC (permalink / raw)
  To: Johannes Schindelin, Torsten Bögershausen
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> This is the second iteration of the long-awaited re-roll of the attempt to
> avoid spawning merge-recursive from the builtin am and use merge_recursive()
> directly instead.

I wanted to queue this in 'pu', but an unfortunate series that
changes the convert_to_git() infrastructure has serious conflicts
with the changes in this series.  I am still unsure if these
changes cannot be done without butchering the calling convention
of what leads to convert_to_git(), but in the meantime, this cannot
yet be merged to 'pu'.


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 00/17] Use merge_recursive() directly in the builtin am
  2016-07-06 21:26   ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Junio C Hamano
@ 2016-07-07 11:16     ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 11:16 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Torsten Bögershausen, git, Eric Sunshine, Jeff King,
	Johannes Sixt, Duy Nguyen

Hi Junio,

On Wed, 6 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > This is the second iteration of the long-awaited re-roll of the attempt to
> > avoid spawning merge-recursive from the builtin am and use merge_recursive()
> > directly instead.
> 
> I wanted to queue this in 'pu', but an unfortunate series that
> changes the convert_to_git() infrastructure has serious conflicts
> with the changes in this series.  I am still unsure if these
> changes cannot be done without butchering the calling convention
> of what leads to convert_to_git(), but in the meantime, this cannot
> yet be merged to 'pu'.

There is nothing butchered there. The secret to the merge conflicts is the
sha1 -> oid conversion paired with Vasco's untranslating of BUG: reports
without upcasing the "bug:" prefix.

I will rebase to `pu` and re-send.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH 2/9] merge-recursive: clarify code in was_tracked()
  2016-07-06 15:30           ` Junio C Hamano
@ 2016-07-07 11:17             ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 11:17 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi Junio,

On Wed, 6 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > To understand why we're not done yet, the crucial point is *not* that the
> > return value encodes the insert position. The crucial point is that
> > despite asking for an index entry matching a specific name, we might not
> > find one, *even if there is one*.
> 
> I've been wondering why you keep saying "even though we didn't ask,
> we look for stage#0", and now I see why.  The cache_pos() interface
> *is* about finding the stage#0 entry for the given path.

Good that this is clarified now.

> [...]
> As you pointed out, we can return early without falling into the
> generic "we are still looking at the same path" codepath when we
> find thestage#0 entry, so I wouldn't mind doing something like the
> following.
> [...]

As this is essentially what I wrote with some minor touch-ups, I just
replaced my version with yours.

Will resend,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 02/17] Report bugs consistently
  2016-07-06 15:30     ` Duy Nguyen
@ 2016-07-07 11:23       ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 11:23 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Eric Sunshine, Jeff King,
	Johannes Sixt

Hi Duy,

On Wed, 6 Jul 2016, Duy Nguyen wrote:

> On Tue, Jul 5, 2016 at 1:23 PM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > The vast majority of error messages in Git's source code which report a
> > bug use the convention to prefix the message with "BUG:".
> >
> > As part of cleaning up merge-recursive to stop die()ing except in case of
> > detected bugs, let's just make the remainder of the bug reports consistent
> > with the de facto rule.
> 
> If you search 'die(_("bug:' in this patch,  you'll find 5 instances
> where _() should be gone too.

I should have known better. Vasco's i18n work already conflicts seriously
with what I have in this patch series.

This proves once again, beyond any doubt, that one should refrain from
introducing patches that have little to do with the purpose of the patch
series.

So as far as this here patch series goes, let's re-focus on the recursive
merge code again, okay?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 11/17] am: counteract gender bias
  2016-07-06 21:22     ` Junio C Hamano
@ 2016-07-07 11:30       ` Johannes Schindelin
  2016-07-07 15:26         ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 11:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen

Hi Junio,

On Wed, 6 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), i.e. for
> > almost 11 years already,...
> > ...Let's start changing that by using the variable name "her_tree" for an
> > equal number of years out of fairness, and change to the gender neutral
> > "their_tree" after that.
> 
> I doubt this kind fo distraction is desirable in the middle of a
> seriously heavy series like this one.  As a standalone clean-up to
> turn these directly to "their" that everybody would agree on and can
> be merged down quickly to 'master' that does not have to keep the
> body of the main topic waiting for the dust to settle might be a
> better approach.
> 
> Unless you are trying to discourage the reviewers, that is ;-).

Funny. In other comments, I am asked to patch things that are truly
unrelated to the patch series' intent, and here I am asked to refrain from
cleaning up the code before I touch it.

I am really curious, though. Has it not been our practice to encourage
preparatory patches like white-space or const fixes as part of patch
series that touch a certain part of the code that needed fixing? I deem
this here patch to be much, much more important than a mere white-space or
const fix.

Since you asked so nicely, I will break out this patch from the patch
series, of course, but please note that it will now look as if I willfully
snuck in an unrelated change in the next patch, just because I was not
allowed to prepare the code properly.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-05 11:22 ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Johannes Schindelin
                     ` (17 preceding siblings ...)
  2016-07-06 21:26   ` [PATCH v2 00/17] Use merge_recursive() directly in the builtin am Junio C Hamano
@ 2016-07-07 14:35   ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
                       ` (17 more replies)
  18 siblings, 18 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

This is the second iteration of the long-awaited re-roll of the attempt to
avoid spawning merge-recursive from the builtin am and use merge_recursive()
directly instead.

The *real* reason for the reroll is that I need a libified recursive
merge to accelerate the interactive rebase by teaching the sequencer to
do rebase -i's grunt work.

In this endeavor, we need to be extra careful to retain backwards
compatibility. The test script t6022-merge-rename.sh, for example, verifies
that `git pull` exits with status 128 in case of a fatal error. To that end,
we need to make sure that fatal errors are handled by existing (builtin)
users via exit(128) (or die(), which calls exit(128) at the end).  New users
(such as a builtin helper doing rebase -i's grunt work) may want to print
some helpful advice what happened and how to get out of this mess before
erroring out.

The changes relative to the second iteration of this patch series:

- the was_tracked() function was adjusted as per Junio's suggestions

- the "counter gender bias" patch was submitted separately, in
  $gmane/299009 (please note that the "am -3: use merge_recursive()
  directly again" patch is now slightly awkward as a consequence)

- this patch series is on top of 'pu', to address the conflicts with
  the 'jh/clean-smudge-annex' and the 'bc/cocci' branches

- please note that the interdiff does not show the full picture: I
  generated it relative to v2 rebased on top of pu (resolving many
  merge conflicts in the process that are hidden from the interdiff)

This patch series touches rather important code. Now that I addressed
concerns such as fixing translated bug reports, I would appreciate thorough
reviews with a focus on the critical parts of the code, those that could
result in regressions.


Johannes Schindelin (16):
  Verify that `git pull --rebase` shows the helpful advice when failing
  Report bugs consistently
  Avoid translating bug messages
  merge-recursive: clarify code in was_tracked()
  Prepare the builtins for a libified merge_recursive()
  merge_recursive: abort properly upon errors
  merge-recursive: avoid returning a wholesale struct
  merge-recursive: allow write_tree_from_memory() to error out
  merge-recursive: handle return values indicating errors
  merge-recursive: switch to returning errors instead of dying
  am -3: use merge_recursive() directly again
  merge-recursive: flush output buffer before printing error messages
  merge-recursive: write the commit title in one go
  merge-recursive: offer an option to retain the output in 'obuf'
  Ensure that the output buffer is released after calling merge_trees()
  merge-recursive: flush output buffer even when erroring out

 builtin/am.c           |  63 +++---
 builtin/checkout.c     |   5 +-
 builtin/ls-files.c     |   3 +-
 builtin/merge.c        |   2 +
 builtin/update-index.c |   2 +-
 grep.c                 |   8 +-
 imap-send.c            |   4 +-
 merge-recursive.c      | 508 +++++++++++++++++++++++++++++--------------------
 merge-recursive.h      |   2 +-
 sequencer.c            |   5 +
 sha1_file.c            |   4 +-
 t/t5520-pull.sh        |  30 +++
 trailer.c              |   2 +-
 transport.c            |   2 +-
 wt-status.c            |   4 +-
 15 files changed, 382 insertions(+), 262 deletions(-)

Published-As: https://github.com/dscho/git/releases/tag/am-3-merge-recursive-direct-v3
Interdiff vs v2:

 diff --git a/builtin/am.c b/builtin/am.c
 index d626532..8dc4239 100644
 --- a/builtin/am.c
 +++ b/builtin/am.c
 @@ -29,6 +29,7 @@
  #include "prompt.h"
  #include "mailinfo.h"
  #include "apply.h"
 +#include "object.h"
  
  /**
   * Returns the length of the first line of msg.
 @@ -1627,15 +1628,14 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
   */
  static int fall_back_threeway(const struct am_state *state, const char *index_path)
  {
 -	unsigned char orig_tree[GIT_SHA1_RAWSZ], her_tree[GIT_SHA1_RAWSZ],
 -		      our_tree[GIT_SHA1_RAWSZ];
 -	const unsigned char *bases[1] = {orig_tree};
 +	struct object_id orig_tree, her_tree, our_tree;
 +	const struct object_id *bases[1] = { &orig_tree };
  	struct merge_options o;
  	struct commit *result;
  	char *her_tree_name;
  
 -	if (get_sha1("HEAD", our_tree) < 0)
 -		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
 +	if (get_oid("HEAD", &our_tree) < 0)
 +		hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
  
  	if (build_fake_ancestor(state, index_path))
  		return error("could not build fake ancestor");
 @@ -1643,7 +1643,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  	discard_cache();
  	read_cache_from(index_path);
  
 -	if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
 +	if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
  		return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
  
  	say(state, stdout, _("Using index info to reconstruct a base tree..."));
 @@ -1659,7 +1659,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  		init_revisions(&rev_info, NULL);
  		rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
  		diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
 -		add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
 +		add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
  		diff_setup_done(&rev_info.diffopt);
  		run_diff_index(&rev_info, 1);
  	}
 @@ -1668,7 +1668,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  		return error(_("Did you hand edit your patch?\n"
  				"It does not apply to blobs recorded in its index."));
  
 -	if (write_index_as_tree(her_tree, &the_index, index_path, 0, NULL))
 +	if (write_index_as_tree(her_tree.hash, &the_index, index_path, 0, NULL))
  		return error("could not write tree");
  
  	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
 @@ -1678,7 +1678,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  
  	/*
  	 * This is not so wrong. Depending on which base we picked, orig_tree
 -	 * may be wildly different from ours, but her_tree has the same set of
 +	 * may be wildly different from ours, but his_tree has the same set of
  	 * wildly different changes in parts the patch did not touch, so
  	 * recursive ends up canceling them, saying that we reverted all those
  	 * changes.
 @@ -1693,7 +1693,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  	if (state->quiet)
  		o.verbosity = 0;
  
 -	if (merge_recursive_generic(&o, our_tree, her_tree, 1, bases, &result)) {
 +	if (merge_recursive_generic(&o, &our_tree, &her_tree, 1, bases, &result)) {
  		rerere(state->allow_rerere_autoupdate);
  		free(her_tree_name);
  		return error(_("Failed to merge in the changes."));
 diff --git a/merge-recursive.c b/merge-recursive.c
 index f80ad35..cb701ee 100644
 --- a/merge-recursive.c
 +++ b/merge-recursive.c
 @@ -686,20 +686,19 @@ static int was_tracked(const char *path)
  {
  	int pos = cache_name_pos(path, strlen(path));
  
 -	if (pos >= 0)
 -		return pos < active_nr;
 +	if (0 <= pos)
 +		/* we have been tracking this path */
 +		return 1;
 +
  	/*
 -	 * cache_name_pos() looks for stage == 0, even if we did not ask for
 -	 * it. Let's look for stage == 2 now.
 +	 * Look for an unmerged entry for the path,
 +	 * specifically stage #2, which would indicate
 +	 * that "our" side before the merge started
 +	 * had the path tracked (and resulted in a conflict).
  	 */
 -	for (pos = -1 - pos; pos < active_nr &&
 -	     !strcmp(path, active_cache[pos]->name); pos++)
 -		/*
 -		 * If stage #0, it is definitely tracked.
 -		 * If it has stage #2 then it was tracked
 -		 * before this merge started.  All other
 -		 * cases the path was not tracked.
 -		 */
 +	for (pos = -1 - pos;
 +	     pos < active_nr && !strcmp(path, active_cache[pos]->name);
 +	     pos++)
  		if (ce_stage(active_cache[pos]) == 2)
  			return 1;
  	return 0;

-- 
2.9.0.278.g1caae67

base-commit: 6addd022ce5331ee7dc41781ded714e5d5f01206

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v3 01/16] Verify that `git pull --rebase` shows the helpful advice when failing
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 02/16] Report bugs consistently Johannes Schindelin
                       ` (16 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5520-pull.sh | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 5d4880e..217b416 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -255,6 +255,36 @@ test_expect_success '--rebase' '
 	test new = "$(git show HEAD:file2)"
 '
 
+test_expect_success '--rebase with conflicts shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b seq &&
+	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&
+	git add seq.txt &&
+	test_tick &&
+	git commit -m "Add seq.txt" &&
+	printf "6\\n" >>seq.txt &&
+	test_tick &&
+	git commit -m "Append to seq.txt" seq.txt &&
+	git checkout -b with-conflicts HEAD^ &&
+	printf "conflicting\\n" >>seq.txt &&
+	test_tick &&
+	git commit -m "Create conflict" seq.txt &&
+	test_must_fail git pull --rebase . seq 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+test_expect_success 'failed --rebase shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b diverging &&
+	test_commit attributes .gitattributes "* text=auto" attrs &&
+	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
+	git update-index --cacheinfo 0644 $sha1 file &&
+	git commit -m v1-with-cr &&
+	git checkout -f -b fails-to-rebase HEAD^ &&
+	test_commit v2-without-cr file "2" file2-lf &&
+	test_must_fail git pull --rebase . diverging 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+
 test_expect_success '--rebase fails with multiple branches' '
 	git reset --hard before-rebase &&
 	test_must_fail git pull --rebase . copy master 2>err &&
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 02/16] Report bugs consistently
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 03/16] Avoid translating bug messages Johannes Schindelin
                       ` (15 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The vast majority of error messages in Git's source code which report a
bug use the convention to prefix the message with "BUG:".

As part of cleaning up merge-recursive to stop die()ing except in case of
detected bugs, let's just make the remainder of the bug reports consistent
with the de facto rule.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/ls-files.c     |  3 ++-
 builtin/update-index.c |  2 +-
 grep.c                 |  8 ++++----
 imap-send.c            |  4 ++--
 merge-recursive.c      | 15 +++++++--------
 sha1_file.c            |  4 ++--
 trailer.c              |  2 +-
 transport.c            |  2 +-
 wt-status.c            |  4 ++--
 9 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f02e3d2..00ea91a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
 				 */
 				pos = cache_name_pos(ent->name, ent->len);
 				if (0 <= pos)
-					die("bug in show-killed-files");
+					die("BUG: killed-file %.*s not found",
+						ent->len, ent->name);
 				pos = -pos - 1;
 				while (pos < active_nr &&
 				       ce_stage(active_cache[pos]))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 25af040..881a4b0 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -1149,7 +1149,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
 		break;
 	default:
-		die("Bug: bad untracked_cache value: %d", untracked_cache);
+		die("BUG: bad untracked_cache value: %d", untracked_cache);
 	}
 
 	if (use_watchman > 0) {
diff --git a/grep.c b/grep.c
index 394c856..22cbb73 100644
--- a/grep.c
+++ b/grep.c
@@ -693,10 +693,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 	for (p = opt->header_list; p; p = p->next) {
 		if (p->token != GREP_PATTERN_HEAD)
-			die("bug: a non-header pattern in grep header list.");
+			die("BUG: a non-header pattern in grep header list.");
 		if (p->field < GREP_HEADER_FIELD_MIN ||
 		    GREP_HEADER_FIELD_MAX <= p->field)
-			die("bug: unknown header field %d", p->field);
+			die("BUG: unknown header field %d", p->field);
 		compile_regexp(p, opt);
 	}
 
@@ -709,7 +709,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 		h = compile_pattern_atom(&pp);
 		if (!h || pp != p->next)
-			die("bug: malformed header expr");
+			die("BUG: malformed header expr");
 		if (!header_group[p->field]) {
 			header_group[p->field] = h;
 			continue;
@@ -1514,7 +1514,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 		case GREP_BINARY_TEXT:
 			break;
 		default:
-			die("bug: unknown binary handling mode");
+			die("BUG: unknown binary handling mode");
 		}
 	}
 
diff --git a/imap-send.c b/imap-send.c
index db0fafe..67d67f8 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -506,12 +506,12 @@ static char *next_arg(char **s)
 
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
-	int ret;
+	int ret = -1;
 	va_list va;
 
 	va_start(va, fmt);
 	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-		die("Fatal: buffer too small. Please report a bug.");
+		die("BUG: buffer too small (%d < %d)", ret, blen);
 	va_end(va);
 	return ret;
 }
diff --git a/merge-recursive.c b/merge-recursive.c
index 067f656..05b9789 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -259,7 +259,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 				fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
 					(int)ce_namelen(ce), ce->name);
 		}
-		die("Bug in merge-recursive.c");
+		die("BUG: unmerged index entries in merge-recursive.c");
 	}
 
 	if (!active_cache_tree)
@@ -982,9 +982,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 
 			if (!oid_eq(&a->oid, &b->oid))
 				result.clean = 0;
-		} else {
-			die(_("unsupported object type in the tree"));
-		}
+		} else
+			die(_("BUG: unsupported object type in the tree"));
 	}
 
 	return result;
@@ -1370,7 +1369,7 @@ static int process_renames(struct merge_options *o,
 			const char *ren2_dst = ren2->pair->two->path;
 			enum rename_type rename_type;
 			if (strcmp(ren1_src, ren2_src) != 0)
-				die("ren1_src != ren2_src");
+				die("BUG: ren1_src != ren2_src");
 			ren2->dst_entry->processed = 1;
 			ren2->processed = 1;
 			if (strcmp(ren1_dst, ren2_dst) != 0) {
@@ -1404,7 +1403,7 @@ static int process_renames(struct merge_options *o,
 			ren2 = lookup->util;
 			ren2_dst = ren2->pair->two->path;
 			if (strcmp(ren1_dst, ren2_dst) != 0)
-				die("ren1_dst != ren2_dst");
+				die("BUG: ren1_dst != ren2_dst");
 
 			clean_merge = 0;
 			ren2->processed = 1;
@@ -1828,7 +1827,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("Fatal merge failure, shouldn't happen."));
+		die(_("BUG: fatal merge failure, shouldn't happen."));
 
 	return clean_merge;
 }
@@ -1886,7 +1885,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("Unprocessed path??? %s"),
+				die(_("BUG: unprocessed path??? %s"),
 				    entries->items[i].string);
 		}
 
diff --git a/sha1_file.c b/sha1_file.c
index df62eaf..27dadff 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -795,7 +795,7 @@ void close_all_packs(void)
 
 	for (p = packed_git; p; p = p->next)
 		if (p->do_not_close)
-			die("BUG! Want to close pack marked 'do-not-close'");
+			die("BUG: want to close pack marked 'do-not-close'");
 		else
 			close_pack(p);
 }
@@ -2336,7 +2336,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
 		if (data)
-			die("BUG in unpack_entry: left loop at a valid delta");
+			die("BUG: unpack_entry: left loop at a valid delta");
 		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
diff --git a/trailer.c b/trailer.c
index 8e48a5c..c6ea9ac 100644
--- a/trailer.c
+++ b/trailer.c
@@ -562,7 +562,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 		break;
 	default:
-		die("internal bug in trailer.c");
+		die("BUG: trailer.c: unhandled type %d", type);
 	}
 	return 0;
 }
diff --git a/transport.c b/transport.c
index ad9ac15..d1dd29a 100644
--- a/transport.c
+++ b/transport.c
@@ -577,7 +577,7 @@ void transport_take_over(struct transport *transport,
 	struct git_transport_data *data;
 
 	if (!transport->smart_options)
-		die("Bug detected: Taking over transport requires non-NULL "
+		die("BUG: taking over transport requires non-NULL "
 		    "smart_options field.");
 
 	data = xcalloc(1, sizeof(*data));
diff --git a/wt-status.c b/wt-status.c
index de62ab2..3fb86a4 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die("bug: unhandled unmerged status %x", stagemask);
+		die("BUG: unhandled unmerged status %x", stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die("bug: unhandled diff status %c", status);
+		die("BUG: unhandled diff status %c", status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 03/16] Avoid translating bug messages
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 02/16] Report bugs consistently Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 04/16] merge-recursive: clarify code in was_tracked() Johannes Schindelin
                       ` (14 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

While working on the patch series that avoids die()ing in recursive
merges, the issue came up that bug reports (i.e. die("BUG: ...")
constructs) should never be translated, as the target audience is the
Git developer community, not necessarily the current user, and hence
a translated message would make it *harder* to address the problem.

So let's stop translating the obvious ones. As it is really, really
outside the purview of this patch series to see whether there are more
die() statements that report bugs and are currently translated, that
task is left for another day and patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 05b9789..ea0df22 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -983,7 +983,7 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 			if (!oid_eq(&a->oid, &b->oid))
 				result.clean = 0;
 		} else
-			die(_("BUG: unsupported object type in the tree"));
+			die("BUG: unsupported object type in the tree");
 	}
 
 	return result;
@@ -1827,7 +1827,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("BUG: fatal merge failure, shouldn't happen."));
+		die("BUG: fatal merge failure, shouldn't happen.");
 
 	return clean_merge;
 }
@@ -1885,7 +1885,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("BUG: unprocessed path??? %s"),
+				die("BUG: unprocessed path??? %s",
 				    entries->items[i].string);
 		}
 
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 04/16] merge-recursive: clarify code in was_tracked()
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (2 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 03/16] Avoid translating bug messages Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 05/16] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
                       ` (13 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It can be puzzling to see that was_tracked() asks to get an index entry
by name, but does not take a negative return value for an answer.

The reason we have to do this is that cache_name_pos() only looks for
entries in stage 0, even if nobody asked for any stage in particular.

Let's rewrite the logic a little bit, to handle the easy case early: if
cache_name_pos() returned a non-negative position, we know it is a match,
and we do not even have to compare the name again (cache_name_pos() did
that for us already). We can say right away: yes, this file was tracked.

Only if there was no exact match do we need to look harder for any
matching entry in stage 2.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 30 ++++++++++++++----------------
 1 file changed, 14 insertions(+), 16 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index ea0df22..469741d 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -658,23 +658,21 @@ static int was_tracked(const char *path)
 {
 	int pos = cache_name_pos(path, strlen(path));
 
-	if (pos < 0)
-		pos = -1 - pos;
-	while (pos < active_nr &&
-	       !strcmp(path, active_cache[pos]->name)) {
-		/*
-		 * If stage #0, it is definitely tracked.
-		 * If it has stage #2 then it was tracked
-		 * before this merge started.  All other
-		 * cases the path was not tracked.
-		 */
-		switch (ce_stage(active_cache[pos])) {
-		case 0:
-		case 2:
+	if (0 <= pos)
+		/* we have been tracking this path */
+		return 1;
+
+	/*
+	 * Look for an unmerged entry for the path,
+	 * specifically stage #2, which would indicate
+	 * that "our" side before the merge started
+	 * had the path tracked (and resulted in a conflict).
+	 */
+	for (pos = -1 - pos;
+	     pos < active_nr && !strcmp(path, active_cache[pos]->name);
+	     pos++)
+		if (ce_stage(active_cache[pos]) == 2)
 			return 1;
-		}
-		pos++;
-	}
 	return 0;
 }
 
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 05/16] Prepare the builtins for a libified merge_recursive()
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (3 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 04/16] merge-recursive: clarify code in was_tracked() Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
                       ` (12 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Previously, callers of merge_trees() or merge_recursive() expected that
code to die() with an error message. This used to be okay because we
called those commands from scripts, and had a chance to print out a
message in case the command failed fatally (read: with exit code 128).

As scripting incurs its own set of problems (portability, speed,
idiosynchracies of different shells, limited data structures leading to
inefficient code), we are converting more and more of these scripts into
builtins, using library functions directly.

We already tried to use merge_recursive() directly in the builtin
git-am, for example. Unfortunately, we had to roll it back temporarily
because some of the code in merge-recursive.c still deemed it okay to
call die(), when the builtin am code really wanted to print out a useful
advice after the merge failed fatally. In the next commits, we want to
fix that.

The code touched by this commit expected merge_trees() to die() with
some useful message when there is an error condition, but merge_trees()
is going to be improved by converting all die() calls to return error()
instead (i.e. return value -1 after printing out the message as before),
so that the caller can react more flexibly.

This is a step to prepare for the version of merge_trees() that no
longer dies,  even if we just imitate the previous behavior by calling
exit(128): this is what callers of e.g. `git merge` have come to expect.

Note that the callers of the sequencer (revert and cherry-pick) already
fail fast even for the return value -1; The only difference is that they
now get a chance to say "<command> failed".

A caller of merge_trees() might want handle error messages themselves
(or even suppress them). As this patch is already complex enough, we
leave that change for a later patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 4 +++-
 builtin/merge.c    | 2 ++
 sequencer.c        | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 27c1a05..07dea3b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -567,8 +567,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.ancestor = old->name;
 			o.branch1 = new->name;
 			o.branch2 = "local";
-			merge_trees(&o, new->commit->tree, work,
+			ret = merge_trees(&o, new->commit->tree, work,
 				old->commit->tree, &result);
+			if (ret < 0)
+				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
 			if (ret)
diff --git a/builtin/merge.c b/builtin/merge.c
index 46b88ad..6837e15 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -682,6 +682,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 		hold_locked_index(&lock, 1);
 		clean = merge_recursive(&o, head,
 				remoteheads->item, reversed, &result);
+		if (clean < 0)
+			exit(128);
 		if (active_cache_changed &&
 		    write_locked_index(&the_index, &lock, COMMIT_LOCK))
 			die (_("unable to write %s"), get_index_file());
diff --git a/sequencer.c b/sequencer.c
index cdfac82..286a435 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	if (clean < 0)
+		return clean;
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
@@ -559,6 +561,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
 					 head, &msgbuf, opts);
+		if (res < 0)
+			return res;
 		write_message(&msgbuf, git_path_merge_msg());
 	} else {
 		struct commit_list *common = NULL;
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 06/16] merge_recursive: abort properly upon errors
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (4 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 05/16] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 07/16] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
                       ` (11 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

There are a couple of places where return values indicating errors
are ignored. Let's teach them manners.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 469741d..37c181a 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1974,8 +1974,9 @@ int merge_recursive(struct merge_options *o,
 		saved_b2 = o->branch2;
 		o->branch1 = "Temporary merge branch 1";
 		o->branch2 = "Temporary merge branch 2";
-		merge_recursive(o, merged_common_ancestors, iter->item,
-				NULL, &merged_common_ancestors);
+		if (merge_recursive(o, merged_common_ancestors, iter->item,
+				NULL, &merged_common_ancestors) < 0)
+			return -1;
 		o->branch1 = saved_b1;
 		o->branch2 = saved_b2;
 		o->call_depth--;
@@ -1991,6 +1992,8 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	if (clean < 0)
+		return clean;
 
 	if (o->call_depth) {
 		*result = make_virtual_commit(mrtree, "merged tree");
@@ -2047,6 +2050,9 @@ int merge_recursive_generic(struct merge_options *o,
 	hold_locked_index(lock, 1);
 	clean = merge_recursive(o, head_commit, next_commit, ca,
 			result);
+	if (clean < 0)
+		return clean;
+
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
 		return error(_("Unable to write index."));
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 07/16] merge-recursive: avoid returning a wholesale struct
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (5 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 08/16] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
                       ` (10 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It is technically allowed, as per C89, for functions' return type to
be complete structs (i.e. *not* just pointers to structs).

However, it was just an oversight of this developer when converting
Python code to C code in 6d297f8 (Status update on merge-recursive in
C, 2006-07-08) which introduced such a return type.

Besides, by converting this construct to pass in the struct, we can now
start returning a value that can indicate errors in future patches. This
will help the current effort to libify merge-recursive.c.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 91 +++++++++++++++++++++++++++++--------------------------
 1 file changed, 48 insertions(+), 43 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 37c181a..d9221ce 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -910,47 +910,47 @@ static int merge_3way(struct merge_options *o,
 	return merge_status;
 }
 
-static struct merge_file_info merge_file_1(struct merge_options *o,
+static int merge_file_1(struct merge_options *o,
 					   const struct diff_filespec *one,
 					   const struct diff_filespec *a,
 					   const struct diff_filespec *b,
 					   const char *branch1,
-					   const char *branch2)
+					   const char *branch2,
+					   struct merge_file_info *result)
 {
-	struct merge_file_info result;
-	result.merge = 0;
-	result.clean = 1;
+	result->merge = 0;
+	result->clean = 1;
 
 	if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-		result.clean = 0;
+		result->clean = 0;
 		if (S_ISREG(a->mode)) {
-			result.mode = a->mode;
-			oidcpy(&result.oid, &a->oid);
+			result->mode = a->mode;
+			oidcpy(&result->oid, &a->oid);
 		} else {
-			result.mode = b->mode;
-			oidcpy(&result.oid, &b->oid);
+			result->mode = b->mode;
+			oidcpy(&result->oid, &b->oid);
 		}
 	} else {
 		if (!oid_eq(&a->oid, &one->oid) && !oid_eq(&b->oid, &one->oid))
-			result.merge = 1;
+			result->merge = 1;
 
 		/*
 		 * Merge modes
 		 */
 		if (a->mode == b->mode || a->mode == one->mode)
-			result.mode = b->mode;
+			result->mode = b->mode;
 		else {
-			result.mode = a->mode;
+			result->mode = a->mode;
 			if (b->mode != one->mode) {
-				result.clean = 0;
-				result.merge = 1;
+				result->clean = 0;
+				result->merge = 1;
 			}
 		}
 
 		if (oid_eq(&a->oid, &b->oid) || oid_eq(&a->oid, &one->oid))
-			oidcpy(&result.oid, &b->oid);
+			oidcpy(&result->oid, &b->oid);
 		else if (oid_eq(&b->oid, &one->oid))
-			oidcpy(&result.oid, &a->oid);
+			oidcpy(&result->oid, &a->oid);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
 			int merge_status;
@@ -962,64 +962,65 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 				die(_("Failed to execute internal merge"));
 
 			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result.oid.hash))
+					    blob_type, result->oid.hash))
 				die(_("Unable to add %s to database"),
 				    a->path);
 
 			free(result_buf.ptr);
-			result.clean = (merge_status == 0);
+			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
-			result.clean = merge_submodule(result.oid.hash,
+			result->clean = merge_submodule(result->oid.hash,
 						       one->path,
 						       one->oid.hash,
 						       a->oid.hash,
 						       b->oid.hash,
 						       !o->call_depth);
 		} else if (S_ISLNK(a->mode)) {
-			oidcpy(&result.oid, &a->oid);
+			oidcpy(&result->oid, &a->oid);
 
 			if (!oid_eq(&a->oid, &b->oid))
-				result.clean = 0;
+				result->clean = 0;
 		} else
 			die("BUG: unsupported object type in the tree");
 	}
 
-	return result;
+	return 0;
 }
 
-static struct merge_file_info
-merge_file_special_markers(struct merge_options *o,
+static int merge_file_special_markers(struct merge_options *o,
 			   const struct diff_filespec *one,
 			   const struct diff_filespec *a,
 			   const struct diff_filespec *b,
 			   const char *branch1,
 			   const char *filename1,
 			   const char *branch2,
-			   const char *filename2)
+			   const char *filename2,
+			   struct merge_file_info *mfi)
 {
 	char *side1 = NULL;
 	char *side2 = NULL;
-	struct merge_file_info mfi;
+	int ret;
 
 	if (filename1)
 		side1 = xstrfmt("%s:%s", branch1, filename1);
 	if (filename2)
 		side2 = xstrfmt("%s:%s", branch2, filename2);
 
-	mfi = merge_file_1(o, one, a, b,
-			   side1 ? side1 : branch1, side2 ? side2 : branch2);
+	ret = merge_file_1(o, one, a, b,
+		side1 ? side1 : branch1, side2 ? side2 : branch2, mfi);
 	free(side1);
 	free(side2);
-	return mfi;
+	return ret;
 }
 
-static struct merge_file_info merge_file_one(struct merge_options *o,
+static int merge_file_one(struct merge_options *o,
 					 const char *path,
 					 const struct object_id *o_oid, int o_mode,
 					 const struct object_id *a_oid, int a_mode,
 					 const struct object_id *b_oid, int b_mode,
 					 const char *branch1,
-					 const char *branch2)
+					 const char *branch2,
+					 struct merge_file_info *mfi)
 {
 	struct diff_filespec one, a, b;
 
@@ -1030,7 +1031,7 @@ static struct merge_file_info merge_file_one(struct merge_options *o,
 	a.mode = a_mode;
 	oidcpy(&b.oid, b_oid);
 	b.mode = b_mode;
-	return merge_file_1(o, &one, &a, &b, branch1, branch2);
+	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
 static void handle_change_delete(struct merge_options *o,
@@ -1203,11 +1204,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		struct merge_file_info mfi;
 		struct diff_filespec other;
 		struct diff_filespec *add;
-		mfi = merge_file_one(o, one->path,
+		if (merge_file_one(o, one->path,
 				 &one->oid, one->mode,
 				 &a->oid, a->mode,
 				 &b->oid, b->mode,
-				 ci->branch1, ci->branch2);
+				 ci->branch1, ci->branch2, &mfi))
+			return;
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
@@ -1261,12 +1263,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
-	mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
 					    o->branch1, c1->path,
-					    o->branch2, ci->ren1_other.path);
-	mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+					    o->branch2, ci->ren1_other.path, &mfi_c1) ||
+	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 					    o->branch1, ci->ren2_other.path,
-					    o->branch2, c2->path);
+					    o->branch2, c2->path, &mfi_c2))
+		return;
 
 	if (o->call_depth) {
 		/*
@@ -1489,12 +1492,13 @@ static int process_renames(struct merge_options *o,
 				       ren1_dst, branch2);
 				if (o->call_depth) {
 					struct merge_file_info mfi;
-					mfi = merge_file_one(o, ren1_dst, &null_oid, 0,
+					if (merge_file_one(o, ren1_dst, &null_oid, 0,
 							 &ren1->pair->two->oid,
 							 ren1->pair->two->mode,
 							 &dst_other.oid,
 							 dst_other.mode,
-							 branch1, branch2);
+							 branch1, branch2, &mfi))
+						return -1;
 					output(o, 1, _("Adding merged %s"), ren1_dst);
 					update_file(o, 0, &mfi.oid,
 						    mfi.mode, ren1_dst);
@@ -1652,9 +1656,10 @@ static int merge_content(struct merge_options *o,
 		if (dir_in_way(path, !o->call_depth))
 			df_conflict_remains = 1;
 	}
-	mfi = merge_file_special_markers(o, &one, &a, &b,
+	if (merge_file_special_markers(o, &one, &a, &b,
 					 o->branch1, path1,
-					 o->branch2, path2);
+					 o->branch2, path2, &mfi))
+		return -1;
 
 	if (mfi.clean && !df_conflict_remains &&
 	    oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) {
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 08/16] merge-recursive: allow write_tree_from_memory() to error out
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (6 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 07/16] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 09/16] merge-recursive: handle return values indicating errors Johannes Schindelin
                       ` (9 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It is possible that a tree cannot be written (think: disk full). We
will want to give the caller a chance to clean up instead of letting
the program die() in such a case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index d9221ce..09f4e27 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1903,8 +1903,8 @@ int merge_trees(struct merge_options *o,
 	else
 		clean = 1;
 
-	if (o->call_depth)
-		*result = write_tree_from_memory(o);
+	if (o->call_depth && !(*result = write_tree_from_memory(o)))
+		return -1;
 
 	return clean;
 }
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 09/16] merge-recursive: handle return values indicating errors
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (7 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 08/16] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 10/16] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
                       ` (8 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

We are about to libify the recursive merge machinery, where we only
die() in case of a bug or memory contention. To that end, we must heed
negative return values as indicating errors.

This requires our functions to be careful to pass through error
conditions in call chains, and for quite a few functions this means
that they have to return values to begin with.

The next step will be to convert the places where we currently die() to
return negative values (read: -1) instead.

Note that we ignore errors reported by make_room_for_path(), consistent
with the previous behavior (update_file_flags() used the return value of
make_room_for_path() only to indicate an early return, but not a fatal
error): if the error is really a fatal error, we will notice later; If
not, it was not that serious a problem to begin with. (Witnesses in
favor of this reasoning are t4151-am-abort and t7610-mergetool, which
would start failing if we stopped on errors reported by
make_room_for_path()).

Note: while this patch makes the code slightly less readable in
update_file_flags() (we introduce a new "goto free_buf;" instead of
an explicit "free(buf); return;"), it is a preparatory change for
the next patch where we will convert all of the die() calls in the same
function to go through the free_buf return path instead.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 199 +++++++++++++++++++++++++++++++++---------------------
 1 file changed, 123 insertions(+), 76 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 09f4e27..89eb937 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -733,7 +733,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	return error(msg, path, _(": perhaps a D/F conflict?"));
 }
 
-static void update_file_flags(struct merge_options *o,
+static int update_file_flags(struct merge_options *o,
 			      const struct object_id *oid,
 			      unsigned mode,
 			      const char *path,
@@ -766,8 +766,7 @@ static void update_file_flags(struct merge_options *o,
 
 		if (make_room_for_path(o, path) < 0) {
 			update_wd = 0;
-			free(buf);
-			goto update_index;
+			goto free_buf;
 		}
 		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
 			int fd;
@@ -823,20 +822,22 @@ static void update_file_flags(struct merge_options *o,
 		} else
 			die(_("do not know what to do with %06o %s '%s'"),
 			    mode, oid_to_hex(oid), path);
+ free_buf:
 		free(buf);
 	}
  update_index:
 	if (update_cache)
 		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+	return 0;
 }
 
-static void update_file(struct merge_options *o,
+static int update_file(struct merge_options *o,
 			int clean,
 			const struct object_id *oid,
 			unsigned mode,
 			const char *path)
 {
-	update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
+	return update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
 }
 
 /* Low level file merging, update and removal */
@@ -1034,7 +1035,7 @@ static int merge_file_one(struct merge_options *o,
 	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
-static void handle_change_delete(struct merge_options *o,
+static int handle_change_delete(struct merge_options *o,
 				 const char *path,
 				 const struct object_id *o_oid, int o_mode,
 				 const struct object_id *a_oid, int a_mode,
@@ -1042,6 +1043,7 @@ static void handle_change_delete(struct merge_options *o,
 				 const char *change, const char *change_past)
 {
 	char *renamed = NULL;
+	int ret = 0;
 	if (dir_in_way(path, !o->call_depth)) {
 		renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2);
 	}
@@ -1052,21 +1054,22 @@ static void handle_change_delete(struct merge_options *o,
 		 * correct; since there is no true "middle point" between
 		 * them, simply reuse the base version for virtual merge base.
 		 */
-		remove_file_from_cache(path);
-		update_file(o, 0, o_oid, o_mode, renamed ? renamed : path);
+		ret = remove_file_from_cache(path);
+		if (!ret)
+			ret = update_file(o, 0, o_oid, o_mode, renamed ? renamed : path);
 	} else if (!a_oid) {
 		if (!renamed) {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path);
-			update_file(o, 0, b_oid, b_mode, path);
+			ret = update_file(o, 0, b_oid, b_mode, path);
 		} else {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path, renamed);
-			update_file(o, 0, b_oid, b_mode, renamed);
+			ret = update_file(o, 0, b_oid, b_mode, renamed);
 		}
 	} else {
 		if (!renamed) {
@@ -1079,7 +1082,7 @@ static void handle_change_delete(struct merge_options *o,
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch2, change_past,
 			       o->branch1, o->branch1, path, renamed);
-			update_file(o, 0, a_oid, a_mode, renamed);
+			ret = update_file(o, 0, a_oid, a_mode, renamed);
 		}
 		/*
 		 * No need to call update_file() on path when !renamed, since
@@ -1089,9 +1092,11 @@ static void handle_change_delete(struct merge_options *o,
 		 */
 	}
 	free(renamed);
+
+	return ret;
 }
 
-static void conflict_rename_delete(struct merge_options *o,
+static int conflict_rename_delete(struct merge_options *o,
 				   struct diff_filepair *pair,
 				   const char *rename_branch,
 				   const char *other_branch)
@@ -1111,21 +1116,20 @@ static void conflict_rename_delete(struct merge_options *o,
 		b_mode = dest->mode;
 	}
 
-	handle_change_delete(o,
+	if (handle_change_delete(o,
 			     o->call_depth ? orig->path : dest->path,
 			     &orig->oid, orig->mode,
 			     a_oid, a_mode,
 			     b_oid, b_mode,
-			     _("rename"), _("renamed"));
+			     _("rename"), _("renamed")))
+		return -1;
 
-	if (o->call_depth) {
-		remove_file_from_cache(dest->path);
-	} else {
-		update_stages(dest->path, NULL,
+	if (o->call_depth)
+		return remove_file_from_cache(dest->path);
+	else
+		return update_stages(dest->path, NULL,
 			      rename_branch == o->branch1 ? dest : NULL,
 			      rename_branch == o->branch1 ? NULL : dest);
-	}
-
 }
 
 static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
@@ -1141,7 +1145,7 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 	return target;
 }
 
-static void handle_file(struct merge_options *o,
+static int handle_file(struct merge_options *o,
 			struct diff_filespec *rename,
 			int stage,
 			struct rename_conflict_info *ci)
@@ -1151,6 +1155,7 @@ static void handle_file(struct merge_options *o,
 	const char *cur_branch, *other_branch;
 	struct diff_filespec other;
 	struct diff_filespec *add;
+	int ret;
 
 	if (stage == 2) {
 		dst_entry = ci->dst_entry1;
@@ -1165,7 +1170,8 @@ static void handle_file(struct merge_options *o,
 	add = filespec_from_entry(&other, dst_entry, stage ^ 1);
 	if (add) {
 		char *add_name = unique_path(o, rename->path, other_branch);
-		update_file(o, 0, &add->oid, add->mode, add_name);
+		if (update_file(o, 0, &add->oid, add->mode, add_name))
+			return -1;
 
 		remove_file(o, 0, rename->path, 0);
 		dst_name = unique_path(o, rename->path, cur_branch);
@@ -1176,17 +1182,20 @@ static void handle_file(struct merge_options *o,
 			       rename->path, other_branch, dst_name);
 		}
 	}
-	update_file(o, 0, &rename->oid, rename->mode, dst_name);
-	if (stage == 2)
-		update_stages(rename->path, NULL, rename, add);
+	if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
+		; /* fall through, do allow dst_name to be released */
+	else if (stage == 2)
+		ret = update_stages(rename->path, NULL, rename, add);
 	else
-		update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
+
+	return ret;
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
+static int conflict_rename_rename_1to2(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* One file was renamed in both branches, but to different names. */
@@ -1209,14 +1218,16 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 				 &a->oid, a->mode,
 				 &b->oid, b->mode,
 				 ci->branch1, ci->branch2, &mfi))
-			return;
+			return -1;
+
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
 		 * pathname and then either rename the add-source file to that
 		 * unique path, or use that unique path instead of src here.
 		 */
-		update_file(o, 0, &mfi.oid, mfi.mode, one->path);
+		if (update_file(o, 0, &mfi.oid, mfi.mode, one->path))
+			return -1;
 
 		/*
 		 * Above, we put the merged content at the merge-base's
@@ -1227,22 +1238,26 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * resolving the conflict at that path in its favor.
 		 */
 		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
-		if (add)
-			update_file(o, 0, &add->oid, add->mode, a->path);
+		if (add) {
+			if (update_file(o, 0, &add->oid, add->mode, a->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(a->path);
 		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
-		if (add)
-			update_file(o, 0, &add->oid, add->mode, b->path);
+		if (add) {
+			if (update_file(o, 0, &add->oid, add->mode, b->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(b->path);
-	} else {
-		handle_file(o, a, 2, ci);
-		handle_file(o, b, 3, ci);
-	}
+	} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
+		return -1;
+
+	return 0;
 }
 
-static void conflict_rename_rename_2to1(struct merge_options *o,
+static int conflict_rename_rename_2to1(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* Two files, a & b, were renamed to the same thing, c. */
@@ -1253,6 +1268,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	char *path = c1->path; /* == c2->path */
 	struct merge_file_info mfi_c1;
 	struct merge_file_info mfi_c2;
+	int ret;
 
 	output(o, 1, _("CONFLICT (rename/rename): "
 	       "Rename %s->%s in %s. "
@@ -1269,7 +1285,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 					    o->branch1, ci->ren2_other.path,
 					    o->branch2, c2->path, &mfi_c2))
-		return;
+		return -1;
 
 	if (o->call_depth) {
 		/*
@@ -1280,19 +1296,25 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 		 * again later for the non-recursive merge.
 		 */
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
-		update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, b->path);
+		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
+		if (!ret)
+			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
+				b->path);
 	} else {
 		char *new_path1 = unique_path(o, path, ci->branch1);
 		char *new_path2 = unique_path(o, path, ci->branch2);
 		output(o, 1, _("Renaming %s to %s and %s to %s instead"),
 		       a->path, new_path1, b->path, new_path2);
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
-		update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, new_path2);
+		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
+		if (!ret)
+			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
+				new_path2);
 		free(new_path2);
 		free(new_path1);
 	}
+
+	return ret;
 }
 
 static int process_renames(struct merge_options *o,
@@ -1477,12 +1499,13 @@ static int process_renames(struct merge_options *o,
 				 * update_file_flags() instead of
 				 * update_file().
 				 */
-				update_file_flags(o,
+				if (update_file_flags(o,
 						  &ren1->pair->two->oid,
 						  ren1->pair->two->mode,
 						  ren1_dst,
 						  1, /* update_cache */
-						  0  /* update_wd    */);
+						  0  /* update_wd    */))
+					clean_merge = -1;
 			} else if (!oid_eq(&dst_other.oid, &null_oid)) {
 				clean_merge = 0;
 				try_merge = 1;
@@ -1497,22 +1520,28 @@ static int process_renames(struct merge_options *o,
 							 ren1->pair->two->mode,
 							 &dst_other.oid,
 							 dst_other.mode,
-							 branch1, branch2, &mfi))
-						return -1;
+							 branch1, branch2, &mfi)) {
+						clean_merge = -1;
+						goto cleanup_and_return;
+					}
 					output(o, 1, _("Adding merged %s"), ren1_dst);
-					update_file(o, 0, &mfi.oid,
-						    mfi.mode, ren1_dst);
+					if (update_file(o, 0, &mfi.oid,
+							mfi.mode, ren1_dst))
+						clean_merge = -1;
 					try_merge = 0;
 				} else {
 					char *new_path = unique_path(o, ren1_dst, branch2);
 					output(o, 1, _("Adding as %s instead"), new_path);
-					update_file(o, 0, &dst_other.oid,
-						    dst_other.mode, new_path);
+					if (update_file(o, 0, &dst_other.oid,
+						    dst_other.mode, new_path))
+						clean_merge = -1;
 					free(new_path);
 				}
 			} else
 				try_merge = 1;
 
+			if (clean_merge < 0)
+				goto cleanup_and_return;
 			if (try_merge) {
 				struct diff_filespec *one, *a, *b;
 				src_other.path = (char *)ren1_src;
@@ -1539,6 +1568,7 @@ static int process_renames(struct merge_options *o,
 			}
 		}
 	}
+cleanup_and_return:
 	string_list_clear(&a_by_dst, 0);
 	string_list_clear(&b_by_dst, 0);
 
@@ -1601,13 +1631,13 @@ error_return:
 	return ret;
 }
 
-static void handle_modify_delete(struct merge_options *o,
+static int handle_modify_delete(struct merge_options *o,
 				 const char *path,
 				 struct object_id *o_oid, int o_mode,
 				 struct object_id *a_oid, int a_mode,
 				 struct object_id *b_oid, int b_mode)
 {
-	handle_change_delete(o,
+	return handle_change_delete(o,
 			     path,
 			     o_oid, o_mode,
 			     a_oid, a_mode,
@@ -1686,7 +1716,8 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			update_stages(path, &one, &a, &b);
+			if (update_stages(path, &one, &a, &b))
+				return -1;
 	}
 
 	if (df_conflict_remains) {
@@ -1694,30 +1725,33 @@ static int merge_content(struct merge_options *o,
 		if (o->call_depth) {
 			remove_file_from_cache(path);
 		} else {
-			if (!mfi.clean)
-				update_stages(path, &one, &a, &b);
-			else {
+			if (!mfi.clean) {
+				if (update_stages(path, &one, &a, &b))
+					return -1;
+			} else {
 				int file_from_stage2 = was_tracked(path);
 				struct diff_filespec merged;
 				oidcpy(&merged.oid, &mfi.oid);
 				merged.mode = mfi.mode;
 
-				update_stages(path, NULL,
+				if (update_stages(path, NULL,
 					      file_from_stage2 ? &merged : NULL,
-					      file_from_stage2 ? NULL : &merged);
+					      file_from_stage2 ? NULL : &merged))
+					return -1;
 			}
 
 		}
 		new_path = unique_path(o, path, rename_conflict_info->branch1);
 		output(o, 1, _("Adding as %s instead"), new_path);
-		update_file(o, 0, &mfi.oid, mfi.mode, new_path);
+		if (update_file(o, 0, &mfi.oid, mfi.mode, new_path)) {
+			free(new_path);
+			return -1;
+		}
 		free(new_path);
 		mfi.clean = 0;
-	} else {
-		update_file(o, mfi.clean, &mfi.oid, mfi.mode, path);
-	}
+	} else if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, path))
+		return -1;
 	return mfi.clean;
-
 }
 
 /* Per entry merge function */
@@ -1745,17 +1779,21 @@ static int process_entry(struct merge_options *o,
 			break;
 		case RENAME_DELETE:
 			clean_merge = 0;
-			conflict_rename_delete(o, conflict_info->pair1,
+			if (conflict_rename_delete(o,
+					       conflict_info->pair1,
 					       conflict_info->branch1,
-					       conflict_info->branch2);
+					       conflict_info->branch2))
+				clean_merge = -1;
 			break;
 		case RENAME_ONE_FILE_TO_TWO:
 			clean_merge = 0;
-			conflict_rename_rename_1to2(o, conflict_info);
+			if (conflict_rename_rename_1to2(o, conflict_info))
+				clean_merge = -1;
 			break;
 		case RENAME_TWO_FILES_TO_ONE:
 			clean_merge = 0;
-			conflict_rename_rename_2to1(o, conflict_info);
+			if (conflict_rename_rename_2to1(o, conflict_info))
+				clean_merge = -1;
 			break;
 		default:
 			entry->processed = 0;
@@ -1775,8 +1813,9 @@ static int process_entry(struct merge_options *o,
 		} else {
 			/* Modify/delete; deleted side may have put a directory in the way */
 			clean_merge = 0;
-			handle_modify_delete(o, path, o_oid, o_mode,
-					     a_oid, a_mode, b_oid, b_mode);
+			if (handle_modify_delete(o, path, o_oid, o_mode,
+					     a_oid, a_mode, b_oid, b_mode))
+				clean_merge = -1;
 		}
 	} else if ((!o_oid && a_oid && !b_oid) ||
 		   (!o_oid && !a_oid && b_oid)) {
@@ -1808,14 +1847,16 @@ static int process_entry(struct merge_options *o,
 			output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
 			       "Adding %s as %s"),
 			       conf, path, other_branch, path, new_path);
-			update_file(o, 0, oid, mode, new_path);
-			if (o->call_depth)
+			if (update_file(o, 0, oid, mode, new_path))
+				clean_merge = -1;
+			else if (o->call_depth)
 				remove_file_from_cache(path);
 			free(new_path);
 		} else {
 			output(o, 2, _("Adding %s"), path);
 			/* do not overwrite file if already present */
-			update_file_flags(o, oid, mode, path, 1, !a_oid);
+			if (update_file_flags(o, oid, mode, path, 1, !a_oid))
+				clean_merge = -1;
 		}
 	} else if (a_oid && b_oid) {
 		/* Case C: Added in both (check for same permissions) and */
@@ -1878,12 +1919,18 @@ int merge_trees(struct merge_options *o,
 		re_head  = get_renames(o, head, common, head, merge, entries);
 		re_merge = get_renames(o, merge, common, head, merge, entries);
 		clean = process_renames(o, re_head, re_merge);
+		if (clean < 0)
+			return clean;
 		for (i = entries->nr-1; 0 <= i; i--) {
 			const char *path = entries->items[i].string;
 			struct stage_data *e = entries->items[i].util;
-			if (!e->processed
-				&& !process_entry(o, path, e))
-				clean = 0;
+			if (!e->processed) {
+				int ret = process_entry(o, path, e);
+				if (!ret)
+					clean = 0;
+				else if (ret < 0)
+					return ret;
+			}
 		}
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 10/16] merge-recursive: switch to returning errors instead of dying
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (8 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 09/16] merge-recursive: handle return values indicating errors Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:35     ` [PATCH v3 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
                       ` (7 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The recursive merge machinery is supposed to be a library function, i.e.
it should return an error when it fails. Originally the functions were
part of the builtin "merge-recursive", though, where it was simpler to
call die() and be done with error handling.

The existing callers were already prepared to detect negative return
values to indicate errors and to behave as previously: exit with code 128
(which is the same thing that die() does, after printing the message).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 63 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 36 insertions(+), 27 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 89eb937..7c9f22c 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -266,8 +266,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 		active_cache_tree = cache_tree();
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
-	    cache_tree_update(&the_index, 0) < 0)
-		die(_("error building trees"));
+	    cache_tree_update(&the_index, 0) < 0) {
+		error(_("error building trees"));
+		return NULL;
+	}
 
 	result = lookup_tree(active_cache_tree->sha1);
 
@@ -707,12 +709,10 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	/* Make sure leading directories are created */
 	status = safe_create_leading_directories_const(path);
 	if (status) {
-		if (status == SCLD_EXISTS) {
+		if (status == SCLD_EXISTS)
 			/* something else exists */
-			error(msg, path, _(": perhaps a D/F conflict?"));
-			return -1;
-		}
-		die(msg, path, "");
+			return error(msg, path, _(": perhaps a D/F conflict?"));
+		return error(msg, path, "");
 	}
 
 	/*
@@ -740,6 +740,8 @@ static int update_file_flags(struct merge_options *o,
 			      int update_cache,
 			      int update_wd)
 {
+	int ret = 0;
+
 	if (o->call_depth)
 		update_wd = 0;
 
@@ -760,9 +762,11 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(oid->hash, &type, &size);
 		if (!buf)
-			die(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
-		if (type != OBJ_BLOB)
-			die(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			return error(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
+		if (type != OBJ_BLOB) {
+			ret = error(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			goto free_buf;
+		}
 
 		if (make_room_for_path(o, path) < 0) {
 			update_wd = 0;
@@ -778,8 +782,10 @@ static int update_file_flags(struct merge_options *o,
 			else
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-			if (fd < 0)
-				die_errno(_("failed to open '%s'"), path);
+			if (fd < 0) {
+				ret = error_errno(_("failed to open '%s'"), path);
+				goto free_buf;
+			}
 
 			smudge_to_file = can_smudge_to_file(path);
 
@@ -792,8 +798,10 @@ static int update_file_flags(struct merge_options *o,
 					 * creation. */
 					smudge_to_file = 0;
 					fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-					if (fd < 0)
-						die_errno(_("failed to open '%s'"), path);
+					if (fd < 0) {
+						ret = error_errno(_("failed to open '%s'"), path);
+						goto free_buf;
+					}
 				}
 				else {
 					close(fd);
@@ -817,18 +825,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				die_errno(_("failed to symlink '%s'"), path);
+				ret = error_errno(_("failed to symlink '%s'"), path);
 			free(lnk);
 		} else
-			die(_("do not know what to do with %06o %s '%s'"),
+			ret = error(_("do not know what to do with %06o %s '%s'"),
 			    mode, oid_to_hex(oid), path);
  free_buf:
 		free(buf);
 	}
  update_index:
-	if (update_cache)
+	if (!ret && update_cache)
 		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-	return 0;
+	return ret;
 }
 
 static int update_file(struct merge_options *o,
@@ -954,20 +962,22 @@ static int merge_file_1(struct merge_options *o,
 			oidcpy(&result->oid, &a->oid);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
-			int merge_status;
+			int ret = 0, merge_status;
 
 			merge_status = merge_3way(o, &result_buf, one, a, b,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				die(_("Failed to execute internal merge"));
+				ret = error(_("Failed to execute internal merge"));
 
-			if (write_sha1_file(result_buf.ptr, result_buf.size,
+			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 					    blob_type, result->oid.hash))
-				die(_("Unable to add %s to database"),
-				    a->path);
+				ret = error(_("Unable to add %s to database"),
+					a->path);
 
 			free(result_buf.ptr);
+			if (ret)
+				return ret;
 			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
 			result->clean = merge_submodule(result->oid.hash,
@@ -1899,11 +1909,10 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			die(_("merging of trees %s and %s failed"),
+			error(_("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
-		else
-			exit(128);
+		return -1;
 	}
 
 	if (unmerged_cache()) {
@@ -2034,7 +2043,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			die(_("merge returned no commit"));
+			return error(_("merge returned no commit"));
 	}
 
 	discard_cache();
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 11/16] am -3: use merge_recursive() directly again
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (9 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 10/16] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
@ 2016-07-07 14:35     ` Johannes Schindelin
  2016-07-07 14:36     ` [PATCH v3 12/16] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
                       ` (6 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:35 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Last October, we had to change this code to run `git merge-recursive`
in a child process: git-am wants to print some helpful advice when the
merge failed, but the code in question was not prepared to return, it
die()d instead.

We are finally at a point when the code *is* prepared to return errors,
and can avoid the child process again.

This reverts commit c63d4b2 (am -3: do not let failed merge from
completing the error codepath, 2015-10-09), with the necessary changes
to adjust for the fact that Git's source code changed in the meantime
(such as: using OIDs instead of hashes in the recursive merge). While at
it, the same undesired gender bias is addressed in the same spirit as in
the separately submitted patch in $gmane/299009.

Note: the code now calls merge_recursive_generic() again. Unlike
merge_trees() and merge_recursive(), this function returns 0 upon success,
as most of Git's functions. Therefore, the error value -1 naturally is
handled correctly, and we do not have to take care of it specifically.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/am.c | 63 ++++++++++++++++++++++--------------------------------------
 1 file changed, 23 insertions(+), 40 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index f9a724e..8dc4239 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -29,6 +29,7 @@
 #include "prompt.h"
 #include "mailinfo.h"
 #include "apply.h"
+#include "object.h"
 
 /**
  * Returns the length of the first line of msg.
@@ -1623,47 +1624,18 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 }
 
 /**
- * Do the three-way merge using fake ancestor, his tree constructed
- * from the fake ancestor and the postimage of the patch, and our
- * state.
- */
-static int run_fallback_merge_recursive(const struct am_state *state,
-					unsigned char *orig_tree,
-					unsigned char *our_tree,
-					unsigned char *his_tree)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-	int status;
-
-	cp.git_cmd = 1;
-
-	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
-			 sha1_to_hex(his_tree), linelen(state->msg), state->msg);
-	if (state->quiet)
-		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
-
-	argv_array_push(&cp.args, "merge-recursive");
-	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
-	argv_array_push(&cp.args, "--");
-	argv_array_push(&cp.args, sha1_to_hex(our_tree));
-	argv_array_push(&cp.args, sha1_to_hex(his_tree));
-
-	status = run_command(&cp) ? (-1) : 0;
-	discard_cache();
-	read_cache();
-	return status;
-}
-
-/**
  * Attempt a threeway merge, using index_path as the temporary index.
  */
 static int fall_back_threeway(const struct am_state *state, const char *index_path)
 {
-	unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
-		      our_tree[GIT_SHA1_RAWSZ];
+	struct object_id orig_tree, her_tree, our_tree;
+	const struct object_id *bases[1] = { &orig_tree };
+	struct merge_options o;
+	struct commit *result;
+	char *her_tree_name;
 
-	if (get_sha1("HEAD", our_tree) < 0)
-		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
+	if (get_oid("HEAD", &our_tree) < 0)
+		hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
 
 	if (build_fake_ancestor(state, index_path))
 		return error("could not build fake ancestor");
@@ -1671,7 +1643,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	discard_cache();
 	read_cache_from(index_path);
 
-	if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
 		return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
 
 	say(state, stdout, _("Using index info to reconstruct a base tree..."));
@@ -1687,7 +1659,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		init_revisions(&rev_info, NULL);
 		rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
 		diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
-		add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
+		add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
 		diff_setup_done(&rev_info.diffopt);
 		run_diff_index(&rev_info, 1);
 	}
@@ -1696,7 +1668,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		return error(_("Did you hand edit your patch?\n"
 				"It does not apply to blobs recorded in its index."));
 
-	if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(her_tree.hash, &the_index, index_path, 0, NULL))
 		return error("could not write tree");
 
 	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
@@ -1712,11 +1684,22 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	 * changes.
 	 */
 
-	if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) {
+	init_merge_options(&o);
+
+	o.branch1 = "HEAD";
+	her_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+	o.branch2 = her_tree_name;
+
+	if (state->quiet)
+		o.verbosity = 0;
+
+	if (merge_recursive_generic(&o, &our_tree, &her_tree, 1, bases, &result)) {
 		rerere(state->allow_rerere_autoupdate);
+		free(her_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
 
+	free(her_tree_name);
 	return 0;
 }
 
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 12/16] merge-recursive: flush output buffer before printing error messages
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (10 preceding siblings ...)
  2016-07-07 14:35     ` [PATCH v3 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
@ 2016-07-07 14:36     ` Johannes Schindelin
  2016-07-07 14:36     ` [PATCH v3 13/16] merge-recursive: write the commit title in one go Johannes Schindelin
                       ` (5 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:36 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The data structure passed to the recursive merge machinery has a feature
where the caller can ask for the output to be buffered into a strbuf, by
setting the field 'buffer_output'.

Previously, we simply swallowed the buffered output when showing error
messages. With this patch, we show the output first, and only then print
the error message.

Currently, the only user of that buffering is merge_recursive() itself,
to avoid the progress output to interfere.

In the next patches, we will introduce a new buffer_output mode that
forces merge_recursive() to retain the output buffer for further
processing by the caller. If the caller asked for that, we will then
also write the error messages into the output buffer. This is necessary
to give the caller more control not only how to react in case of errors
but also control how/if to display the error messages.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 112 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 66 insertions(+), 46 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 7c9f22c..205ea04 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -23,6 +23,28 @@
 #include "dir.h"
 #include "submodule.h"
 
+static void flush_output(struct merge_options *o)
+{
+	if (o->obuf.len) {
+		fputs(o->obuf.buf, stdout);
+		strbuf_reset(&o->obuf);
+	}
+}
+
+static int err(struct merge_options *o, const char *err, ...)
+{
+	va_list params;
+
+	va_start(params, err);
+	flush_output(o);
+	strbuf_vaddf(&o->obuf, err, params);
+	error("%s", o->obuf.buf);
+	strbuf_reset(&o->obuf);
+	va_end(params);
+
+	return -1;
+}
+
 static struct tree *shift_tree_object(struct tree *one, struct tree *two,
 				      const char *subtree_shift)
 {
@@ -148,14 +170,6 @@ static int show(struct merge_options *o, int v)
 	return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
 }
 
-static void flush_output(struct merge_options *o)
-{
-	if (o->obuf.len) {
-		fputs(o->obuf.buf, stdout);
-		strbuf_reset(&o->obuf);
-	}
-}
-
 __attribute__((format (printf, 3, 4)))
 static void output(struct merge_options *o, int v, const char *fmt, ...)
 {
@@ -198,7 +212,8 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
 	}
 }
 
-static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
+static int add_cacheinfo(struct merge_options *o,
+		unsigned int mode, const struct object_id *oid,
 		const char *path, int stage, int refresh, int options)
 {
 	struct cache_entry *ce;
@@ -206,7 +221,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
 			      (refresh ? (CE_MATCH_REFRESH |
 					  CE_MATCH_IGNORE_MISSING) : 0 ));
 	if (!ce)
-		return error(_("addinfo_cache failed for path '%s'"), path);
+		return err(o, _("addinfo_cache failed for path '%s'"), path);
 	return add_cache_entry(ce, options);
 }
 
@@ -267,7 +282,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
 	    cache_tree_update(&the_index, 0) < 0) {
-		error(_("error building trees"));
+		err(o, _("error building trees"));
 		return NULL;
 	}
 
@@ -535,7 +550,8 @@ static struct string_list *get_renames(struct merge_options *o,
 	return renames;
 }
 
-static int update_stages(const char *path, const struct diff_filespec *o,
+static int update_stages(struct merge_options *opt, const char *path,
+			 const struct diff_filespec *o,
 			 const struct diff_filespec *a,
 			 const struct diff_filespec *b)
 {
@@ -554,13 +570,13 @@ static int update_stages(const char *path, const struct diff_filespec *o,
 		if (remove_file_from_cache(path))
 			return -1;
 	if (o)
-		if (add_cacheinfo(o->mode, &o->oid, path, 1, 0, options))
+		if (add_cacheinfo(opt, o->mode, &o->oid, path, 1, 0, options))
 			return -1;
 	if (a)
-		if (add_cacheinfo(a->mode, &a->oid, path, 2, 0, options))
+		if (add_cacheinfo(opt, a->mode, &a->oid, path, 2, 0, options))
 			return -1;
 	if (b)
-		if (add_cacheinfo(b->mode, &b->oid, path, 3, 0, options))
+		if (add_cacheinfo(opt, b->mode, &b->oid, path, 3, 0, options))
 			return -1;
 	return 0;
 }
@@ -711,8 +727,8 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	if (status) {
 		if (status == SCLD_EXISTS)
 			/* something else exists */
-			return error(msg, path, _(": perhaps a D/F conflict?"));
-		return error(msg, path, "");
+			return err(o, msg, path, _(": perhaps a D/F conflict?"));
+		return err(o, msg, path, "");
 	}
 
 	/*
@@ -720,7 +736,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	 * tracking it.
 	 */
 	if (would_lose_untracked(path))
-		return error(_("refusing to lose untracked file at '%s'"),
+		return err(o, _("refusing to lose untracked file at '%s'"),
 			     path);
 
 	/* Successful unlink is good.. */
@@ -730,7 +746,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	if (errno == ENOENT)
 		return 0;
 	/* .. but not some other error (who really cares what?) */
-	return error(msg, path, _(": perhaps a D/F conflict?"));
+	return err(o, msg, path, _(": perhaps a D/F conflict?"));
 }
 
 static int update_file_flags(struct merge_options *o,
@@ -762,9 +778,9 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(oid->hash, &type, &size);
 		if (!buf)
-			return error(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
+			return err(o, _("cannot read object %s '%s'"), oid_to_hex(oid), path);
 		if (type != OBJ_BLOB) {
-			ret = error(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			ret = err(o, _("blob expected for %s '%s'"), oid_to_hex(oid), path);
 			goto free_buf;
 		}
 
@@ -783,7 +799,8 @@ static int update_file_flags(struct merge_options *o,
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
 			if (fd < 0) {
-				ret = error_errno(_("failed to open '%s'"), path);
+				ret = err(o, _("failed to open '%s': %s"),
+					path, strerror(errno));
 				goto free_buf;
 			}
 
@@ -825,17 +842,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				ret = error_errno(_("failed to symlink '%s'"), path);
+				ret = err(o, _("failed to symlink '%s': %s"),
+					path, strerror(errno));
 			free(lnk);
 		} else
-			ret = error(_("do not know what to do with %06o %s '%s'"),
-			    mode, oid_to_hex(oid), path);
+			ret = err(o, _("do not know what to do with %06o %s '%s'"),
+				mode, oid_to_hex(oid), path);
  free_buf:
 		free(buf);
 	}
  update_index:
 	if (!ret && update_cache)
-		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+		add_cacheinfo(o, mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 	return ret;
 }
 
@@ -968,11 +986,11 @@ static int merge_file_1(struct merge_options *o,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				ret = error(_("Failed to execute internal merge"));
+				ret = err(o, _("Failed to execute internal merge"));
 
 			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 					    blob_type, result->oid.hash))
-				ret = error(_("Unable to add %s to database"),
+				ret = err(o, _("Unable to add %s to database"),
 					a->path);
 
 			free(result_buf.ptr);
@@ -1137,7 +1155,7 @@ static int conflict_rename_delete(struct merge_options *o,
 	if (o->call_depth)
 		return remove_file_from_cache(dest->path);
 	else
-		return update_stages(dest->path, NULL,
+		return update_stages(o, dest->path, NULL,
 			      rename_branch == o->branch1 ? dest : NULL,
 			      rename_branch == o->branch1 ? NULL : dest);
 }
@@ -1195,9 +1213,9 @@ static int handle_file(struct merge_options *o,
 	if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
 		; /* fall through, do allow dst_name to be released */
 	else if (stage == 2)
-		ret = update_stages(rename->path, NULL, rename, add);
+		ret = update_stages(o, rename->path, NULL, rename, add);
 	else
-		ret = update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(o, rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
@@ -1590,23 +1608,25 @@ static struct object_id *stage_oid(const struct object_id *oid, unsigned mode)
 	return (is_null_oid(oid) || mode == 0) ? NULL: (struct object_id *)oid;
 }
 
-static int read_oid_strbuf(const struct object_id *oid, struct strbuf *dst)
+static int read_oid_strbuf(struct merge_options *o,
+	const struct object_id *oid, struct strbuf *dst)
 {
 	void *buf;
 	enum object_type type;
 	unsigned long size;
 	buf = read_sha1_file(oid->hash, &type, &size);
 	if (!buf)
-		return error(_("cannot read object %s"), oid_to_hex(oid));
+		return err(o, _("cannot read object %s"), oid_to_hex(oid));
 	if (type != OBJ_BLOB) {
 		free(buf);
-		return error(_("object %s is not a blob"), oid_to_hex(oid));
+		return err(o, _("object %s is not a blob"), oid_to_hex(oid));
 	}
 	strbuf_attach(dst, buf, size, size + 1);
 	return 0;
 }
 
-static int blob_unchanged(const struct object_id *o_oid,
+static int blob_unchanged(struct merge_options *opt,
+			  const struct object_id *o_oid,
 			  unsigned o_mode,
 			  const struct object_id *a_oid,
 			  unsigned a_mode,
@@ -1624,7 +1644,7 @@ static int blob_unchanged(const struct object_id *o_oid,
 		return 0;
 
 	assert(o_oid && a_oid);
-	if (read_oid_strbuf(o_oid, &o) || read_oid_strbuf(a_oid, &a))
+	if (read_oid_strbuf(opt, o_oid, &o) || read_oid_strbuf(opt, a_oid, &a))
 		goto error_return;
 	/*
 	 * Note: binary | is used so that both renormalizations are
@@ -1713,7 +1733,7 @@ static int merge_content(struct merge_options *o,
 		 */
 		path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
 		if (!path_renamed_outside_HEAD) {
-			add_cacheinfo(mfi.mode, &mfi.oid, path,
+			add_cacheinfo(o, mfi.mode, &mfi.oid, path,
 				      0, (!o->call_depth), 0);
 			return mfi.clean;
 		}
@@ -1726,7 +1746,7 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			if (update_stages(path, &one, &a, &b))
+			if (update_stages(o, path, &one, &a, &b))
 				return -1;
 	}
 
@@ -1736,7 +1756,7 @@ static int merge_content(struct merge_options *o,
 			remove_file_from_cache(path);
 		} else {
 			if (!mfi.clean) {
-				if (update_stages(path, &one, &a, &b))
+				if (update_stages(o, path, &one, &a, &b))
 					return -1;
 			} else {
 				int file_from_stage2 = was_tracked(path);
@@ -1744,7 +1764,7 @@ static int merge_content(struct merge_options *o,
 				oidcpy(&merged.oid, &mfi.oid);
 				merged.mode = mfi.mode;
 
-				if (update_stages(path, NULL,
+				if (update_stages(o, path, NULL,
 					      file_from_stage2 ? &merged : NULL,
 					      file_from_stage2 ? NULL : &merged))
 					return -1;
@@ -1812,8 +1832,8 @@ static int process_entry(struct merge_options *o,
 	} else if (o_oid && (!a_oid || !b_oid)) {
 		/* Case A: Deleted in one */
 		if ((!a_oid && !b_oid) ||
-		    (!b_oid && blob_unchanged(o_oid, o_mode, a_oid, a_mode, normalize, path)) ||
-		    (!a_oid && blob_unchanged(o_oid, o_mode, b_oid, b_mode, normalize, path))) {
+		    (!b_oid && blob_unchanged(o, o_oid, o_mode, a_oid, a_mode, normalize, path)) ||
+		    (!a_oid && blob_unchanged(o, o_oid, o_mode, b_oid, b_mode, normalize, path))) {
 			/* Deleted in both or deleted in one and
 			 * unchanged in the other */
 			if (a_oid)
@@ -1909,7 +1929,7 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			error(_("merging of trees %s and %s failed"),
+			err(o, _("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
 		return -1;
@@ -2043,7 +2063,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			return error(_("merge returned no commit"));
+			return err(o, _("merge returned no commit"));
 	}
 
 	discard_cache();
@@ -2102,7 +2122,7 @@ int merge_recursive_generic(struct merge_options *o,
 		for (i = 0; i < num_base_list; ++i) {
 			struct commit *base;
 			if (!(base = get_ref(base_list[i], oid_to_hex(base_list[i]))))
-				return error(_("Could not parse object '%s'"),
+				return err(o, _("Could not parse object '%s'"),
 					oid_to_hex(base_list[i]));
 			commit_list_insert(base, &ca);
 		}
@@ -2116,7 +2136,7 @@ int merge_recursive_generic(struct merge_options *o,
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
-		return error(_("Unable to write index."));
+		return err(o, _("Unable to write index."));
 
 	return clean ? 0 : 1;
 }
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 13/16] merge-recursive: write the commit title in one go
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (11 preceding siblings ...)
  2016-07-07 14:36     ` [PATCH v3 12/16] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
@ 2016-07-07 14:36     ` Johannes Schindelin
  2016-07-07 14:36     ` [PATCH v3 14/16] merge-recursive: offer an option to retain the output in 'obuf' Johannes Schindelin
                       ` (4 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:36 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

In 66a155b (Enable output buffering in merge-recursive., 2007-01-14), we
changed the code such that it prints the output in one go, to avoid
interfering with the progress output.

Let's make sure that the same holds true when outputting the commit
title: previously, we used several printf() statements to stdout and
speculated that stdout's buffer is large enough to hold the entire
commit title.

Apart from making that speculation unnecessary, we change the code to
add the message to the output buffer before flushing for another reason:
the next commit will introduce a new level of output buffering, where
the caller can request the output not to be flushed, but to be retained
for further processing.

This latter feature will be needed when teaching the sequencer to do
rebase -i's brunt work: it wants to control the output of the
cherry-picks (i.e. recursive merges).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 205ea04..ad5b961 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -191,25 +191,26 @@ static void output(struct merge_options *o, int v, const char *fmt, ...)
 
 static void output_commit_title(struct merge_options *o, struct commit *commit)
 {
-	int i;
-	flush_output(o);
-	for (i = o->call_depth; i--;)
-		fputs("  ", stdout);
+	strbuf_addchars(&o->obuf, ' ', o->call_depth * 2);
 	if (commit->util)
-		printf("virtual %s\n", merge_remote_util(commit)->name);
+		strbuf_addf(&o->obuf, "virtual %s\n",
+			merge_remote_util(commit)->name);
 	else {
-		printf("%s ", find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
+		strbuf_addf(&o->obuf, "%s ",
+			find_unique_abbrev(commit->object.oid.hash,
+				DEFAULT_ABBREV));
 		if (parse_commit(commit) != 0)
-			printf(_("(bad commit)\n"));
+			strbuf_addf(&o->obuf, _("(bad commit)\n"));
 		else {
 			const char *title;
 			const char *msg = get_commit_buffer(commit, NULL);
 			int len = find_commit_subject(msg, &title);
 			if (len)
-				printf("%.*s\n", len, title);
+				strbuf_addf(&o->obuf, "%.*s\n", len, title);
 			unuse_commit_buffer(commit, msg);
 		}
 	}
+	flush_output(o);
 }
 
 static int add_cacheinfo(struct merge_options *o,
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 14/16] merge-recursive: offer an option to retain the output in 'obuf'
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (12 preceding siblings ...)
  2016-07-07 14:36     ` [PATCH v3 13/16] merge-recursive: write the commit title in one go Johannes Schindelin
@ 2016-07-07 14:36     ` Johannes Schindelin
  2016-07-07 14:36     ` [PATCH v3 15/16] Ensure that the output buffer is released after calling merge_trees() Johannes Schindelin
                       ` (3 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:36 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Since 66a155b (Enable output buffering in merge-recursive., 2007-01-14),
we already accumulate the output in a buffer. The idea was to avoid
interfering with the progress output that goes to stderr, which is
unbuffered, when we write to stdout, which is buffered.

We extend that buffering to allow the caller to handle the output
(possibly suppressing it). This will help us when extending the
sequencer to do rebase -i's brunt work: it does not want the picks to
print anything by default but instead determine itself whether to print
the output or not.

Note that we also redirect the error messages into the output buffer
when the caller asked not to flush the output buffer, for two reasons:
1) to retain the correct output order, and 2) to allow the caller to
suppress *all* output.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 +++++++++++++----
 merge-recursive.h |  2 +-
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index ad5b961..10d3355 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -25,7 +25,7 @@
 
 static void flush_output(struct merge_options *o)
 {
-	if (o->obuf.len) {
+	if (o->buffer_output < 2 && o->obuf.len) {
 		fputs(o->obuf.buf, stdout);
 		strbuf_reset(&o->obuf);
 	}
@@ -36,10 +36,19 @@ static int err(struct merge_options *o, const char *err, ...)
 	va_list params;
 
 	va_start(params, err);
-	flush_output(o);
+	if (o->buffer_output < 2)
+		flush_output(o);
+	else {
+		strbuf_complete(&o->obuf, '\n');
+		strbuf_addstr(&o->obuf, "error: ");
+	}
 	strbuf_vaddf(&o->obuf, err, params);
-	error("%s", o->obuf.buf);
-	strbuf_reset(&o->obuf);
+	if (o->buffer_output > 1)
+		strbuf_addch(&o->obuf, '\n');
+	else {
+		error("%s", o->obuf.buf);
+		strbuf_reset(&o->obuf);
+	}
 	va_end(params);
 
 	return -1;
diff --git a/merge-recursive.h b/merge-recursive.h
index d415724..340704c 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -13,7 +13,7 @@ struct merge_options {
 		MERGE_RECURSIVE_THEIRS
 	} recursive_variant;
 	const char *subtree_shift;
-	unsigned buffer_output : 1;
+	unsigned buffer_output : 2; /* 1: output at end, 2: keep buffered */
 	unsigned renormalize : 1;
 	long xdl_opts;
 	int verbosity;
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 15/16] Ensure that the output buffer is released after calling merge_trees()
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (13 preceding siblings ...)
  2016-07-07 14:36     ` [PATCH v3 14/16] merge-recursive: offer an option to retain the output in 'obuf' Johannes Schindelin
@ 2016-07-07 14:36     ` Johannes Schindelin
  2016-07-07 14:36     ` [PATCH v3 16/16] merge-recursive: flush output buffer even when erroring out Johannes Schindelin
                       ` (2 subsequent siblings)
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:36 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The recursive merge machinery accumulates its output in an output
buffer, to be flushed at the end of merge_recursive(). At this point,
we forgot to release the output buffer.

When calling merge_trees() (i.e. the non-recursive part of the recursive
merge) directly, the output buffer is never flushed because the caller
may be merge_recursive() which wants to flush the output itself.

For the same reason, merge_trees() cannot release the output buffer: it
may still be needed.

Forgetting to release the output buffer did not matter much when running
git-checkout, or git-merge-recursive, because we exited after the
operation anyway. Ever since cherry-pick learned to pick a commit range,
however, this memory leak had the potential of becoming a problem.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 1 +
 merge-recursive.c  | 2 ++
 sequencer.c        | 1 +
 3 files changed, 4 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 07dea3b..8d852d4 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -573,6 +573,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
+			strbuf_release(&o.obuf);
 			if (ret)
 				return ret;
 		}
diff --git a/merge-recursive.c b/merge-recursive.c
index 10d3355..c0be0c5 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2092,6 +2092,8 @@ int merge_recursive(struct merge_options *o,
 		commit_list_insert(h2, &(*result)->parents->next);
 	}
 	flush_output(o);
+	if (o->buffer_output < 2)
+		strbuf_release(&o->obuf);
 	if (show(o, 2))
 		diff_warn_rename_limit("merge.renamelimit",
 				       o->needed_rename_limit, 0);
diff --git a/sequencer.c b/sequencer.c
index 286a435..ec50519 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	strbuf_release(&o.obuf);
 	if (clean < 0)
 		return clean;
 
-- 
2.9.0.278.g1caae67



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v3 16/16] merge-recursive: flush output buffer even when erroring out
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (14 preceding siblings ...)
  2016-07-07 14:36     ` [PATCH v3 15/16] Ensure that the output buffer is released after calling merge_trees() Johannes Schindelin
@ 2016-07-07 14:36     ` Johannes Schindelin
  2016-07-12 21:27     ` [PATCH v3 00/16] Use merge_recursive() directly in the builtin am Junio C Hamano
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
  17 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 14:36 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Ever since 66a155b (Enable output buffering in merge-recursive.,
2007-01-14), we had a problem: When the merge failed in a fatal way, all
regular output was swallowed because we called die() and did not get a
chance to drain the output buffers.

To fix this, several modifications were necessary:

- we needed to stop die()ing, to give callers a chance to do something
  when an error occurred (in this case, flush the output buffers),

- we needed to delay printing the error message so that the caller can
  print the buffered output before that, and

- we needed to make sure that the output buffers are flushed even when
  the return value indicates an error.

The first two changes were introduced through earlier commits in this
patch series, and this commit addresses the third one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index c0be0c5..cb701ee 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2083,6 +2083,7 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	flush_output(o);
 	if (clean < 0)
 		return clean;
 
@@ -2091,7 +2092,6 @@ int merge_recursive(struct merge_options *o,
 		commit_list_insert(h1, &(*result)->parents);
 		commit_list_insert(h2, &(*result)->parents->next);
 	}
-	flush_output(o);
 	if (o->buffer_output < 2)
 		strbuf_release(&o->obuf);
 	if (show(o, 2))
-- 
2.9.0.278.g1caae67

^ permalink raw reply related	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 11/17] am: counteract gender bias
  2016-07-07 11:30       ` Johannes Schindelin
@ 2016-07-07 15:26         ` Junio C Hamano
  2016-07-07 15:54           ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-07 15:26 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> I doubt this kind fo distraction is desirable in the middle of a
>> seriously heavy series like this one.  As a standalone clean-up to
>> turn these directly to "their" that everybody would agree on and can
>> be merged down quickly to 'master' that does not have to keep the
>> body of the main topic waiting for the dust to settle might be a
>> better approach.
>>  ...
> I am really curious, though. Has it not been our practice to encourage
> preparatory patches like white-space or const fixes as part of patch
> series that touch a certain part of the code that needed fixing?

Yes, isn't that "preparatory clean-up" what I said "might be a
better approach"?

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 11/17] am: counteract gender bias
  2016-07-07 15:26         ` Junio C Hamano
@ 2016-07-07 15:54           ` Johannes Schindelin
  2016-07-07 16:03             ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-07 15:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen

Hi Junio,

On Thu, 7 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> I doubt this kind fo distraction is desirable in the middle of a
> >> seriously heavy series like this one.  As a standalone clean-up to
> >> turn these directly to "their" that everybody would agree on and can
> >> be merged down quickly to 'master' that does not have to keep the
> >> body of the main topic waiting for the dust to settle might be a
> >> better approach.
> >>  ...
> > I am really curious, though. Has it not been our practice to encourage
> > preparatory patches like white-space or const fixes as part of patch
> > series that touch a certain part of the code that needed fixing?
> 
> Yes, isn't that "preparatory clean-up" what I said "might be a
> better approach"?

What I meant by "preparatory clean-up" is a patch in the patch series,
just as I had it.

Now it is a separate patch "series".

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v2 11/17] am: counteract gender bias
  2016-07-07 15:54           ` Johannes Schindelin
@ 2016-07-07 16:03             ` Junio C Hamano
  0 siblings, 0 replies; 262+ messages in thread
From: Junio C Hamano @ 2016-07-07 16:03 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hi Junio,
>
> On Thu, 7 Jul 2016, Junio C Hamano wrote:
>
>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>> 
>> >> I doubt this kind fo distraction is desirable in the middle of a
>> >> seriously heavy series like this one.  As a standalone clean-up to
>> >> turn these directly to "their" that everybody would agree on and can
>> >> be merged down quickly to 'master' that does not have to keep the
>> >> body of the main topic waiting for the dust to settle might be a
>> >> better approach.
>> >>  ...
>> > I am really curious, though. Has it not been our practice to encourage
>> > preparatory patches like white-space or const fixes as part of patch
>> > series that touch a certain part of the code that needed fixing?
>> 
>> Yes, isn't that "preparatory clean-up" what I said "might be a
>> better approach"?
>
> What I meant by "preparatory clean-up" is a patch in the patch series,
> just as I had it.
>
> Now it is a separate patch "series".

The main thing I had trouble with was to have it in the middle at
11/17.  Since I do not have time to reorder other people's patches,
every time you reroll the main series, this step will need to be
looked at together with others.  Having it near the beginning as
preparatory clean-up, or even better a separate series that is an
uncontroversial preparatory clean-up that the main topic depends on,
is what I meant.

You build the main "series" on top of what 'master' would have when
the separate one that "does not have to keep the body of the main
topic waiting" gets merged.  Because the separate one would be
something "everybody would agree on and can be merged down quickly",
that will not slow the main topic down.



^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (15 preceding siblings ...)
  2016-07-07 14:36     ` [PATCH v3 16/16] merge-recursive: flush output buffer even when erroring out Johannes Schindelin
@ 2016-07-12 21:27     ` Junio C Hamano
  2016-07-14 14:03       ` Johannes Schindelin
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
  17 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-12 21:27 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> This is the second iteration of the long-awaited re-roll of the attempt to
> avoid spawning merge-recursive from the builtin am and use merge_recursive()
> directly instead.

This is actually the third iteration.

I am trying to tease dependencies apart and apply this on a more
reasonable base than a commit that happened to be at 'pu' on one
day, but this would probably take some time, and I may give up
merging it anywhere for today's integration cycle.  We'll see.






^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-12 21:27     ` [PATCH v3 00/16] Use merge_recursive() directly in the builtin am Junio C Hamano
@ 2016-07-14 14:03       ` Johannes Schindelin
  2016-07-14 19:39         ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-14 14:03 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Hi Junio,

On Tue, 12 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > This is the second iteration of the long-awaited re-roll of the attempt to
> > avoid spawning merge-recursive from the builtin am and use merge_recursive()
> > directly instead.
> 
> This is actually the third iteration.

It is.

> I am trying to tease dependencies apart and apply this on a more
> reasonable base than a commit that happened to be at 'pu' on one
> day, but this would probably take some time, and I may give up
> merging it anywhere for today's integration cycle.  We'll see.

The two topics that are in 'pu' and conflict with this series are
'jh/clean-smudge-annex' and 'bc/cocci'.

It also conflicted with 'va/i18n-even-more', but that one was merged to
'master'.

Now, I think it would be okay to wait for 'bc/cocci' to go to 'master'
before integrating the 'am-3-merge-recursive-direct' branch, but I would
want to avoid waiting for 'jh/clean-smudge-annex'.

Do you concur? If so, I will rebase onto 'master' as soon as 'bc/cocci'
lands in there.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-14 14:03       ` Johannes Schindelin
@ 2016-07-14 19:39         ` Junio C Hamano
  2016-07-19  0:17           ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-14 19:39 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski, Joey Hess

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> The two topics that are in 'pu' and conflict with this series are
> 'jh/clean-smudge-annex' and 'bc/cocci'.
>
> It also conflicted with 'va/i18n-even-more', but that one was merged to
> 'master'.
>
> Now, I think it would be okay to wait for 'bc/cocci' to go to 'master'
> before integrating the 'am-3-merge-recursive-direct' branch, but I would
> want to avoid waiting for 'jh/clean-smudge-annex'.
>
> Do you concur? If so, I will rebase onto 'master' as soon as 'bc/cocci'
> lands in there.

I do not have a strong preference myself.  As you are not proposing
to eject jh/clean-smudge-annex from my tree, I'd have to resolve the
conflicts when the second topic is merged after one topic, whichever
happens to be more ready.  IOW, such a rebase adds to my workload.

Like it or not, it is normal for different topics to want to touch
the overlapping area or one topic to invalidate an assumption the
other topic is based on, and working well together does not have to
mean leaving the conflict resolution to the sole responsibility of
the integrator.  A clean way forward may be for everybody involved
(and I see you forgot to add Joey to this thread) to agree that this
topic is more ready than jh/clean-smudge-annex and you and Joey to
work together to rebase jh/clean-smudge-annex on top of this topic
(or possibly the other way around), making him wait for you.


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-14 19:39         ` Junio C Hamano
@ 2016-07-19  0:17           ` Junio C Hamano
  2016-07-19 12:31             ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-19  0:17 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski, Joey Hess

Junio C Hamano <gitster@pobox.com> writes:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
>> The two topics that are in 'pu' and conflict with this series are
>> 'jh/clean-smudge-annex' and 'bc/cocci'.
>>
>> It also conflicted with 'va/i18n-even-more', but that one was merged to
>> 'master'.
>>
>> Now, I think it would be okay to wait for 'bc/cocci' to go to 'master'
>> before integrating the 'am-3-merge-recursive-direct' branch, but I would
>> want to avoid waiting for 'jh/clean-smudge-annex'.

Nothing seems to be happening on jh/clean-smudge-annex front, so
unless we hear otherwise from Joey in the coming couple of days,
let's get js/am-3-merge-recursive-direct untangled from its
dependencies and get it into a shape to hit 'next'.  You can assume
that I'll merge bc/cocci and js/am-call-theirs-theirs-in-fallback-3way
to 'master' during that time, so an appropriate base to use would be
the result of

    git checkout master^0
    git merge bc/cocci
    git merge js/am-call-theirs-theirs-in-fallback-3way
    git merge cc/apply-am

if you want a semi-solid base to rebuild am-3-merge-recursive-direct
on.  I am not 100% sure about the doneness of cc/apply-am yet,
though but it could be that am-3-merge-recursive-direct does not
have to depend on it at all.  You'd know better than I do ;-)

After that, I'll see if the conflict resolution is manageable when
remerging jh/clean-smudge-annex to 'pu', and if it is not, I'll
worry about it at that point.

Thanks.


^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-19  0:17           ` Junio C Hamano
@ 2016-07-19 12:31             ` Johannes Schindelin
  2016-07-19 14:28               ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-19 12:31 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski, Joey Hess

Hi Junio,

On Mon, 18 Jul 2016, Junio C Hamano wrote:

> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> >
> >> The two topics that are in 'pu' and conflict with this series are
> >> 'jh/clean-smudge-annex' and 'bc/cocci'.
> >>
> >> It also conflicted with 'va/i18n-even-more', but that one was merged to
> >> 'master'.
> >>
> >> Now, I think it would be okay to wait for 'bc/cocci' to go to 'master'
> >> before integrating the 'am-3-merge-recursive-direct' branch, but I would
> >> want to avoid waiting for 'jh/clean-smudge-annex'.
> 
> Nothing seems to be happening on jh/clean-smudge-annex front, so
> unless we hear otherwise from Joey in the coming couple of days,
> let's get js/am-3-merge-recursive-direct untangled from its
> dependencies and get it into a shape to hit 'next'.

Okay, I will rebase and re-submit.

> You can assume that I'll merge bc/cocci and
> js/am-call-theirs-theirs-in-fallback-3way to 'master' during that time,
> so an appropriate base to use would be the result of
> 
>     git checkout master^0
>     git merge bc/cocci
>     git merge js/am-call-theirs-theirs-in-fallback-3way
>     git merge cc/apply-am
> 
> if you want a semi-solid base to rebuild am-3-merge-recursive-direct
> on.

Okay. The way my `mail-patch-series.sh` script works, however, is that it
infers the base commit by testing which tip among pu, next and master is
the most appropriate.

So I guess I will have to hack it up a bit to allow basing my patch series
on something custom.

> I am not 100% sure about the doneness of cc/apply-am yet, though but it
> could be that am-3-merge-recursive-direct does not have to depend on it
> at all.  You'd know better than I do ;-)

I agree on the doneness of cc/apply-am (as you know, I tried to help it
along but my comments had an adverse effect).

And no: nothing in my entire rebase--helper patches relies on cc/apply-am
(I do not even believe that anything conflicts with it, either).

> After that, I'll see if the conflict resolution is manageable when
> remerging jh/clean-smudge-annex to 'pu', and if it is not, I'll worry
> about it at that point.

I can help with that, too.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-19 12:31             ` Johannes Schindelin
@ 2016-07-19 14:28               ` Johannes Schindelin
  2016-07-19 19:33                 ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-19 14:28 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski, Joey Hess

Hi Junio,

On Tue, 19 Jul 2016, Johannes Schindelin wrote:

> On Mon, 18 Jul 2016, Junio C Hamano wrote:
> 
> > Junio C Hamano <gitster@pobox.com> writes:
> > 
> > You can assume that I'll merge bc/cocci and
> > js/am-call-theirs-theirs-in-fallback-3way to 'master' during that time,
> > so an appropriate base to use would be the result of
> > 
> >     git checkout master^0
> >     git merge bc/cocci
> >     git merge js/am-call-theirs-theirs-in-fallback-3way
> >     git merge cc/apply-am
> > 
> > if you want a semi-solid base to rebuild am-3-merge-recursive-direct
> > on.

I do not need cc/apply-am at all, it turns out, but my patch series has a
minor conflict with 'jc/renormalize-merge-kill-safer-crlf'.

Since you indicated that you want to cook that branch a bit in 'next'
first, I will rebase to master+bc/cocci+js/am-call-theirs-theirs and
re-submit.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v3 00/16] Use merge_recursive() directly in the builtin am
  2016-07-19 14:28               ` Johannes Schindelin
@ 2016-07-19 19:33                 ` Junio C Hamano
  0 siblings, 0 replies; 262+ messages in thread
From: Junio C Hamano @ 2016-07-19 19:33 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski, Joey Hess

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> I do not need cc/apply-am at all, it turns out, but my patch series has a
> minor conflict with 'jc/renormalize-merge-kill-safer-crlf'.
>
> Since you indicated that you want to cook that branch a bit in 'next'
> first, I will rebase to master+bc/cocci+js/am-call-theirs-theirs and
> re-submit.

Thanks.  I suspect the renomalization would graduate earlier than
the topic in question, but leaving dependency to the minimum and
have rerere take care of minor conflicts has proved to be a better
approach in general over time; the base you chose above sounds
appropriate.

Thanks.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v4 00/16] Use merge_recursive() directly in the builtin am
  2016-07-07 14:35   ` [PATCH v3 00/16] " Johannes Schindelin
                       ` (16 preceding siblings ...)
  2016-07-12 21:27     ` [PATCH v3 00/16] Use merge_recursive() directly in the builtin am Junio C Hamano
@ 2016-07-22 12:23     ` Johannes Schindelin
  2016-07-22 12:24       ` [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
                         ` (16 more replies)
  17 siblings, 17 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:23 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

This is the fourth iteration of the long-awaited re-roll of the attempt to
avoid spawning merge-recursive from the builtin am and use merge_recursive()
directly instead.

The *real* reason for the reroll is that I need a libified recursive
merge to accelerate the interactive rebase by teaching the sequencer to
do rebase -i's grunt work. Coming with a very nice 3x-5x speedup of
`rebase -i`.

In this endeavor, we need to be extra careful to retain backwards
compatibility. The test script t6022-merge-rename.sh, for example, verifies
that `git pull` exits with status 128 in case of a fatal error. To that end,
we need to make sure that fatal errors are handled by existing (builtin)
users via exit(128) (or die(), which calls exit(128) at the end).  New users
(such as a builtin helper doing rebase -i's grunt work) may want to print
some helpful advice what happened and how to get out of this mess before
erroring out.

The changes relative to the second iteration of this patch series (I
have a feeling that nobody reviewed the 3rd iteration because it was
based on `pu`):

- the was_tracked() function was adjusted as per Junio's suggestions

- the "counter gender bias" patch was submitted, and accepted,
  separately, even if the version we settled on sends a much weaker
  message than I would have preferred

- this patch series is on top of 'master' again

- a test was introduced to verify that we do not reintroduce the bug
  which required our hot fix to spawn the recursive merge again

- while at it, I fixed a ton of incorrect indentations that I missed in
  the first three iterations

- as the bc/cocci branch was merged, a couple of fixups were necessary
  to the patch that avoids spawning the recursive merge

- the interdiff is relative to v2 rebased onto master. That means that
  I had to simulate the "her_tree" change for the sake of the interdiff.

This patch series touches rather important code. Now that I addressed
concerns such as fixing translated bug reports, I would appreciate thorough
reviews with a focus on the critical parts of the code, those that could
result in regressions.


Johannes Schindelin (16):
  Verify that `git pull --rebase` shows the helpful advice when failing
  Report bugs consistently
  Avoid translating bug messages
  merge-recursive: clarify code in was_tracked()
  Prepare the builtins for a libified merge_recursive()
  merge_recursive: abort properly upon errors
  merge-recursive: avoid returning a wholesale struct
  merge-recursive: allow write_tree_from_memory() to error out
  merge-recursive: handle return values indicating errors
  merge-recursive: switch to returning errors instead of dying
  am -3: use merge_recursive() directly again
  merge-recursive: flush output buffer before printing error messages
  merge-recursive: write the commit title in one go
  merge-recursive: offer an option to retain the output in 'obuf'
  Ensure that the output buffer is released after calling merge_trees()
  merge-recursive: flush output buffer even when erroring out

 builtin/am.c           |  62 ++----
 builtin/checkout.c     |   5 +-
 builtin/ls-files.c     |   3 +-
 builtin/merge.c        |   2 +
 builtin/update-index.c |   2 +-
 grep.c                 |   8 +-
 imap-send.c            |   4 +-
 merge-recursive.c      | 571 +++++++++++++++++++++++++++++--------------------
 merge-recursive.h      |   2 +-
 sequencer.c            |   5 +
 sha1_file.c            |   4 +-
 t/t5520-pull.sh        |  30 +++
 trailer.c              |   2 +-
 transport.c            |   2 +-
 wt-status.c            |   4 +-
 15 files changed, 413 insertions(+), 293 deletions(-)

Published-As: https://github.com/dscho/git/releases/tag/am-3-merge-recursive-direct-v4
Interdiff vs v3:

 diff --git a/builtin/am.c b/builtin/am.c
 index bf4f79b..cfb79ea 100644
 --- a/builtin/am.c
 +++ b/builtin/am.c
 @@ -1583,15 +1583,14 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
   */
  static int fall_back_threeway(const struct am_state *state, const char *index_path)
  {
 -	unsigned char orig_tree[GIT_SHA1_RAWSZ], her_tree[GIT_SHA1_RAWSZ],
 -		      our_tree[GIT_SHA1_RAWSZ];
 -	const unsigned char *bases[1] = {orig_tree};
 +	struct object_id orig_tree, their_tree, our_tree;
 +	const struct object_id *bases[1] = { &orig_tree };
  	struct merge_options o;
  	struct commit *result;
 -	char *her_tree_name;
 +	char *their_tree_name;
  
 -	if (get_sha1("HEAD", our_tree) < 0)
 -		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
 +	if (get_oid("HEAD", &our_tree) < 0)
 +		hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
  
  	if (build_fake_ancestor(state, index_path))
  		return error("could not build fake ancestor");
 @@ -1599,7 +1598,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  	discard_cache();
  	read_cache_from(index_path);
  
 -	if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
 +	if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
  		return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
  
  	say(state, stdout, _("Using index info to reconstruct a base tree..."));
 @@ -1615,7 +1614,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  		init_revisions(&rev_info, NULL);
  		rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
  		diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
 -		add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
 +		add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
  		diff_setup_done(&rev_info.diffopt);
  		run_diff_index(&rev_info, 1);
  	}
 @@ -1624,7 +1623,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  		return error(_("Did you hand edit your patch?\n"
  				"It does not apply to blobs recorded in its index."));
  
 -	if (write_index_as_tree(her_tree, &the_index, index_path, 0, NULL))
 +	if (write_index_as_tree(their_tree.hash, &the_index, index_path, 0, NULL))
  		return error("could not write tree");
  
  	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
 @@ -1634,7 +1633,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  
  	/*
  	 * This is not so wrong. Depending on which base we picked, orig_tree
 -	 * may be wildly different from ours, but her_tree has the same set of
 +	 * may be wildly different from ours, but their_tree has the same set of
  	 * wildly different changes in parts the patch did not touch, so
  	 * recursive ends up canceling them, saying that we reverted all those
  	 * changes.
 @@ -1643,19 +1642,19 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
  	init_merge_options(&o);
  
  	o.branch1 = "HEAD";
 -	her_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 -	o.branch2 = her_tree_name;
 +	their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 +	o.branch2 = their_tree_name;
  
  	if (state->quiet)
  		o.verbosity = 0;
  
 -	if (merge_recursive_generic(&o, our_tree, her_tree, 1, bases, &result)) {
 +	if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
  		rerere(state->allow_rerere_autoupdate);
 -		free(her_tree_name);
 +		free(their_tree_name);
  		return error(_("Failed to merge in the changes."));
  	}
  
 -	free(her_tree_name);
 +	free(their_tree_name);
  	return 0;
  }
  
 diff --git a/merge-recursive.c b/merge-recursive.c
 index cd2a533..501cfb5 100644
 --- a/merge-recursive.c
 +++ b/merge-recursive.c
 @@ -686,20 +686,19 @@ static int was_tracked(const char *path)
  {
  	int pos = cache_name_pos(path, strlen(path));
  
 -	if (pos >= 0)
 -		return pos < active_nr;
 +	if (0 <= pos)
 +		/* we have been tracking this path */
 +		return 1;
 +
  	/*
 -	 * cache_name_pos() looks for stage == 0, even if we did not ask for
 -	 * it. Let's look for stage == 2 now.
 +	 * Look for an unmerged entry for the path,
 +	 * specifically stage #2, which would indicate
 +	 * that "our" side before the merge started
 +	 * had the path tracked (and resulted in a conflict).
  	 */
 -	for (pos = -1 - pos; pos < active_nr &&
 -	     !strcmp(path, active_cache[pos]->name); pos++)
 -		/*
 -		 * If stage #0, it is definitely tracked.
 -		 * If it has stage #2 then it was tracked
 -		 * before this merge started.  All other
 -		 * cases the path was not tracked.
 -		 */
 +	for (pos = -1 - pos;
 +	     pos < active_nr && !strcmp(path, active_cache[pos]->name);
 +	     pos++)
  		if (ce_stage(active_cache[pos]) == 2)
  			return 1;
  	return 0;
 @@ -761,11 +760,11 @@ static int make_room_for_path(struct merge_options *o, const char *path)
  }
  
  static int update_file_flags(struct merge_options *o,
 -			      const struct object_id *oid,
 -			      unsigned mode,
 -			      const char *path,
 -			      int update_cache,
 -			      int update_wd)
 +			     const struct object_id *oid,
 +			     unsigned mode,
 +			     const char *path,
 +			     int update_cache,
 +			     int update_wd)
  {
  	int ret = 0;
  
 @@ -816,7 +815,7 @@ static int update_file_flags(struct merge_options *o,
  			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
  			if (fd < 0) {
  				ret = err(o, _("failed to open '%s': %s"),
 -					path, strerror(errno));
 +					  path, strerror(errno));
  				goto free_buf;
  			}
  			write_in_full(fd, buf, size);
 @@ -830,8 +829,9 @@ static int update_file_flags(struct merge_options *o,
  					path, strerror(errno));
  			free(lnk);
  		} else
 -			ret = err(o, _("do not know what to do with %06o %s '%s'"),
 -				mode, oid_to_hex(oid), path);
 +			ret = err(o,
 +				  _("do not know what to do with %06o %s '%s'"),
 +				  mode, oid_to_hex(oid), path);
   free_buf:
  		free(buf);
  	}
 @@ -842,10 +842,10 @@ static int update_file_flags(struct merge_options *o,
  }
  
  static int update_file(struct merge_options *o,
 -			int clean,
 -			const struct object_id *oid,
 -			unsigned mode,
 -			const char *path)
 +		       int clean,
 +		       const struct object_id *oid,
 +		       unsigned mode,
 +		       const char *path)
  {
  	return update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
  }
 @@ -973,9 +973,9 @@ static int merge_file_1(struct merge_options *o,
  				ret = err(o, _("Failed to execute internal merge"));
  
  			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 -					    blob_type, result->oid.hash))
 +						    blob_type, result->oid.hash))
  				ret = err(o, _("Unable to add %s to database"),
 -					a->path);
 +					  a->path);
  
  			free(result_buf.ptr);
  			if (ret)
 @@ -1020,7 +1020,8 @@ static int merge_file_special_markers(struct merge_options *o,
  		side2 = xstrfmt("%s:%s", branch2, filename2);
  
  	ret = merge_file_1(o, one, a, b,
 -		side1 ? side1 : branch1, side2 ? side2 : branch2, mfi);
 +			   side1 ? side1 : branch1,
 +			   side2 ? side2 : branch2, mfi);
  	free(side1);
  	free(side2);
  	return ret;
 @@ -1068,7 +1069,8 @@ static int handle_change_delete(struct merge_options *o,
  		 */
  		ret = remove_file_from_cache(path);
  		if (!ret)
 -			ret = update_file(o, 0, o_oid, o_mode, renamed ? renamed : path);
 +			ret = update_file(o, 0, o_oid, o_mode,
 +					  renamed ? renamed : path);
  	} else if (!a_oid) {
  		if (!renamed) {
  			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 @@ -1129,19 +1131,19 @@ static int conflict_rename_delete(struct merge_options *o,
  	}
  
  	if (handle_change_delete(o,
 -			     o->call_depth ? orig->path : dest->path,
 -			     &orig->oid, orig->mode,
 -			     a_oid, a_mode,
 -			     b_oid, b_mode,
 -			     _("rename"), _("renamed")))
 +				 o->call_depth ? orig->path : dest->path,
 +				 &orig->oid, orig->mode,
 +				 a_oid, a_mode,
 +				 b_oid, b_mode,
 +				 _("rename"), _("renamed")))
  		return -1;
  
  	if (o->call_depth)
  		return remove_file_from_cache(dest->path);
  	else
  		return update_stages(o, dest->path, NULL,
 -			      rename_branch == o->branch1 ? dest : NULL,
 -			      rename_branch == o->branch1 ? NULL : dest);
 +				     rename_branch == o->branch1 ? dest : NULL,
 +				     rename_branch == o->branch1 ? NULL : dest);
  }
  
  static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 @@ -1292,11 +1294,11 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
  	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
  
  	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
 -					    o->branch1, c1->path,
 -					    o->branch2, ci->ren1_other.path, &mfi_c1) ||
 +				       o->branch1, c1->path,
 +				       o->branch2, ci->ren1_other.path, &mfi_c1) ||
  	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 -					    o->branch1, ci->ren2_other.path,
 -					    o->branch2, c2->path, &mfi_c2))
 +				       o->branch1, ci->ren2_other.path,
 +				       o->branch2, c2->path, &mfi_c2))
  		return -1;
  
  	if (o->call_depth) {
 @@ -1311,7 +1313,7 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
  		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
  		if (!ret)
  			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
 -				b->path);
 +					  b->path);
  	} else {
  		char *new_path1 = unique_path(o, path, ci->branch1);
  		char *new_path2 = unique_path(o, path, ci->branch2);
 @@ -1321,7 +1323,7 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
  		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
  		if (!ret)
  			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
 -				new_path2);
 +					  new_path2);
  		free(new_path2);
  		free(new_path1);
  	}
 @@ -1512,11 +1514,11 @@ static int process_renames(struct merge_options *o,
  				 * update_file().
  				 */
  				if (update_file_flags(o,
 -						  &ren1->pair->two->oid,
 -						  ren1->pair->two->mode,
 -						  ren1_dst,
 -						  1, /* update_cache */
 -						  0  /* update_wd    */))
 +						      &ren1->pair->two->oid,
 +						      ren1->pair->two->mode,
 +						      ren1_dst,
 +						      1, /* update_cache */
 +						      0  /* update_wd    */))
  					clean_merge = -1;
  			} else if (!oid_eq(&dst_other.oid, &null_oid)) {
  				clean_merge = 0;
 @@ -1528,11 +1530,11 @@ static int process_renames(struct merge_options *o,
  				if (o->call_depth) {
  					struct merge_file_info mfi;
  					if (merge_file_one(o, ren1_dst, &null_oid, 0,
 -							 &ren1->pair->two->oid,
 -							 ren1->pair->two->mode,
 -							 &dst_other.oid,
 -							 dst_other.mode,
 -							 branch1, branch2, &mfi)) {
 +							   &ren1->pair->two->oid,
 +							   ren1->pair->two->mode,
 +							   &dst_other.oid,
 +							   dst_other.mode,
 +							   branch1, branch2, &mfi)) {
  						clean_merge = -1;
  						goto cleanup_and_return;
  					}
 @@ -1545,7 +1547,7 @@ static int process_renames(struct merge_options *o,
  					char *new_path = unique_path(o, ren1_dst, branch2);
  					output(o, 1, _("Adding as %s instead"), new_path);
  					if (update_file(o, 0, &dst_other.oid,
 -						    dst_other.mode, new_path))
 +							dst_other.mode, new_path))
  						clean_merge = -1;
  					free(new_path);
  				}
 @@ -1652,11 +1654,11 @@ static int handle_modify_delete(struct merge_options *o,
  				 struct object_id *b_oid, int b_mode)
  {
  	return handle_change_delete(o,
 -			     path,
 -			     o_oid, o_mode,
 -			     a_oid, a_mode,
 -			     b_oid, b_mode,
 -			     _("modify"), _("modified"));
 +				    path,
 +				    o_oid, o_mode,
 +				    a_oid, a_mode,
 +				    b_oid, b_mode,
 +				    _("modify"), _("modified"));
  }
  
  static int merge_content(struct merge_options *o,
 @@ -1701,8 +1703,8 @@ static int merge_content(struct merge_options *o,
  			df_conflict_remains = 1;
  	}
  	if (merge_file_special_markers(o, &one, &a, &b,
 -					 o->branch1, path1,
 -					 o->branch2, path2, &mfi))
 +				       o->branch1, path1,
 +				       o->branch2, path2, &mfi))
  		return -1;
  
  	if (mfi.clean && !df_conflict_remains &&
 @@ -1749,8 +1751,8 @@ static int merge_content(struct merge_options *o,
  				merged.mode = mfi.mode;
  
  				if (update_stages(o, path, NULL,
 -					      file_from_stage2 ? &merged : NULL,
 -					      file_from_stage2 ? NULL : &merged))
 +						  file_from_stage2 ? &merged : NULL,
 +						  file_from_stage2 ? NULL : &merged))
  					return -1;
  			}
  
 @@ -1794,9 +1796,9 @@ static int process_entry(struct merge_options *o,
  		case RENAME_DELETE:
  			clean_merge = 0;
  			if (conflict_rename_delete(o,
 -					       conflict_info->pair1,
 -					       conflict_info->branch1,
 -					       conflict_info->branch2))
 +						   conflict_info->pair1,
 +						   conflict_info->branch1,
 +						   conflict_info->branch2))
  				clean_merge = -1;
  			break;
  		case RENAME_ONE_FILE_TO_TWO:
 @@ -1828,7 +1830,7 @@ static int process_entry(struct merge_options *o,
  			/* Modify/delete; deleted side may have put a directory in the way */
  			clean_merge = 0;
  			if (handle_modify_delete(o, path, o_oid, o_mode,
 -					     a_oid, a_mode, b_oid, b_mode))
 +						 a_oid, a_mode, b_oid, b_mode))
  				clean_merge = -1;
  		}
  	} else if ((!o_oid && a_oid && !b_oid) ||
 @@ -2040,7 +2042,7 @@ int merge_recursive(struct merge_options *o,
  		o->branch1 = "Temporary merge branch 1";
  		o->branch2 = "Temporary merge branch 2";
  		if (merge_recursive(o, merged_common_ancestors, iter->item,
 -				NULL, &merged_common_ancestors) < 0)
 +				    NULL, &merged_common_ancestors) < 0)
  			return -1;
  		o->branch1 = saved_b1;
  		o->branch2 = saved_b2;

-- 
2.9.0.281.g286a8d9

base-commit: 08bb3500a2a718c3c78b0547c68601cafa7a8fd9

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
@ 2016-07-22 12:24       ` Johannes Schindelin
  2016-07-25 21:39         ` Junio C Hamano
  2016-07-22 12:24       ` [PATCH v4 02/16] Report bugs consistently Johannes Schindelin
                         ` (15 subsequent siblings)
  16 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:24 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It was noticed by Brendan Forster last October that the builtin `git am`
regressed on that. Our hot fix reverted to spawning the recursive merge
instead of using it as a library function.

As we are about to revert that hot fix, after making the recursive merge a
true library function (i.e. a function that does not die() in case of
"normal" errors), let's add a test that verifies that we do not regress on
the same problem which made the hot fix necessary in the first place.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5520-pull.sh | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 37ebbcf..d289056 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -255,6 +255,36 @@ test_expect_success '--rebase' '
 	test new = "$(git show HEAD:file2)"
 '
 
+test_expect_success '--rebase with conflicts shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b seq &&
+	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&
+	git add seq.txt &&
+	test_tick &&
+	git commit -m "Add seq.txt" &&
+	printf "6\\n" >>seq.txt &&
+	test_tick &&
+	git commit -m "Append to seq.txt" seq.txt &&
+	git checkout -b with-conflicts HEAD^ &&
+	printf "conflicting\\n" >>seq.txt &&
+	test_tick &&
+	git commit -m "Create conflict" seq.txt &&
+	test_must_fail git pull --rebase . seq 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+test_expect_success 'failed --rebase shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b diverging &&
+	test_commit attributes .gitattributes "* text=auto" attrs &&
+	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
+	git update-index --cacheinfo 0644 $sha1 file &&
+	git commit -m v1-with-cr &&
+	git checkout -f -b fails-to-rebase HEAD^ &&
+	test_commit v2-without-cr file "2" file2-lf &&
+	test_must_fail git pull --rebase . diverging 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+
 test_expect_success '--rebase fails with multiple branches' '
 	git reset --hard before-rebase &&
 	test_must_fail git pull --rebase . copy master 2>err &&
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 02/16] Report bugs consistently
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
  2016-07-22 12:24       ` [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
@ 2016-07-22 12:24       ` Johannes Schindelin
  2016-07-25 21:44         ` Junio C Hamano
  2016-07-22 12:24       ` [PATCH v4 03/16] Avoid translating bug messages Johannes Schindelin
                         ` (14 subsequent siblings)
  16 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:24 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The vast majority of error messages in Git's source code which report a
bug use the convention to prefix the message with "BUG:".

As part of cleaning up merge-recursive to stop die()ing except in case of
detected bugs, let's just make the remainder of the bug reports consistent
with the de facto rule.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/ls-files.c     |  3 ++-
 builtin/update-index.c |  2 +-
 grep.c                 |  8 ++++----
 imap-send.c            |  4 ++--
 merge-recursive.c      | 15 +++++++--------
 sha1_file.c            |  4 ++--
 trailer.c              |  2 +-
 transport.c            |  2 +-
 wt-status.c            |  4 ++--
 9 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f02e3d2..00ea91a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
 				 */
 				pos = cache_name_pos(ent->name, ent->len);
 				if (0 <= pos)
-					die("bug in show-killed-files");
+					die("BUG: killed-file %.*s not found",
+						ent->len, ent->name);
 				pos = -pos - 1;
 				while (pos < active_nr &&
 				       ce_stage(active_cache[pos]))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 6cdfd5f..ba04b19 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -1146,7 +1146,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
 		break;
 	default:
-		die("Bug: bad untracked_cache value: %d", untracked_cache);
+		die("BUG: bad untracked_cache value: %d", untracked_cache);
 	}
 
 	if (active_cache_changed) {
diff --git a/grep.c b/grep.c
index 394c856..22cbb73 100644
--- a/grep.c
+++ b/grep.c
@@ -693,10 +693,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 	for (p = opt->header_list; p; p = p->next) {
 		if (p->token != GREP_PATTERN_HEAD)
-			die("bug: a non-header pattern in grep header list.");
+			die("BUG: a non-header pattern in grep header list.");
 		if (p->field < GREP_HEADER_FIELD_MIN ||
 		    GREP_HEADER_FIELD_MAX <= p->field)
-			die("bug: unknown header field %d", p->field);
+			die("BUG: unknown header field %d", p->field);
 		compile_regexp(p, opt);
 	}
 
@@ -709,7 +709,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 		h = compile_pattern_atom(&pp);
 		if (!h || pp != p->next)
-			die("bug: malformed header expr");
+			die("BUG: malformed header expr");
 		if (!header_group[p->field]) {
 			header_group[p->field] = h;
 			continue;
@@ -1514,7 +1514,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 		case GREP_BINARY_TEXT:
 			break;
 		default:
-			die("bug: unknown binary handling mode");
+			die("BUG: unknown binary handling mode");
 		}
 	}
 
diff --git a/imap-send.c b/imap-send.c
index db0fafe..67d67f8 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -506,12 +506,12 @@ static char *next_arg(char **s)
 
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
-	int ret;
+	int ret = -1;
 	va_list va;
 
 	va_start(va, fmt);
 	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-		die("Fatal: buffer too small. Please report a bug.");
+		die("BUG: buffer too small (%d < %d)", ret, blen);
 	va_end(va);
 	return ret;
 }
diff --git a/merge-recursive.c b/merge-recursive.c
index 48fe7e7..7400034 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -259,7 +259,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 				fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
 					(int)ce_namelen(ce), ce->name);
 		}
-		die("Bug in merge-recursive.c");
+		die("BUG: unmerged index entries in merge-recursive.c");
 	}
 
 	if (!active_cache_tree)
@@ -957,9 +957,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 
 			if (!oid_eq(&a->oid, &b->oid))
 				result.clean = 0;
-		} else {
-			die(_("unsupported object type in the tree"));
-		}
+		} else
+			die(_("BUG: unsupported object type in the tree"));
 	}
 
 	return result;
@@ -1345,7 +1344,7 @@ static int process_renames(struct merge_options *o,
 			const char *ren2_dst = ren2->pair->two->path;
 			enum rename_type rename_type;
 			if (strcmp(ren1_src, ren2_src) != 0)
-				die("ren1_src != ren2_src");
+				die("BUG: ren1_src != ren2_src");
 			ren2->dst_entry->processed = 1;
 			ren2->processed = 1;
 			if (strcmp(ren1_dst, ren2_dst) != 0) {
@@ -1379,7 +1378,7 @@ static int process_renames(struct merge_options *o,
 			ren2 = lookup->util;
 			ren2_dst = ren2->pair->two->path;
 			if (strcmp(ren1_dst, ren2_dst) != 0)
-				die("ren1_dst != ren2_dst");
+				die("BUG: ren1_dst != ren2_dst");
 
 			clean_merge = 0;
 			ren2->processed = 1;
@@ -1803,7 +1802,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("Fatal merge failure, shouldn't happen."));
+		die(_("BUG: fatal merge failure, shouldn't happen."));
 
 	return clean_merge;
 }
@@ -1861,7 +1860,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("Unprocessed path??? %s"),
+				die(_("BUG: unprocessed path??? %s"),
 				    entries->items[i].string);
 		}
 
diff --git a/sha1_file.c b/sha1_file.c
index d5e1121..5085fe0 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -795,7 +795,7 @@ void close_all_packs(void)
 
 	for (p = packed_git; p; p = p->next)
 		if (p->do_not_close)
-			die("BUG! Want to close pack marked 'do-not-close'");
+			die("BUG: want to close pack marked 'do-not-close'");
 		else
 			close_pack(p);
 }
@@ -2330,7 +2330,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
 		if (data)
-			die("BUG in unpack_entry: left loop at a valid delta");
+			die("BUG: unpack_entry: left loop at a valid delta");
 		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
diff --git a/trailer.c b/trailer.c
index 8e48a5c..c6ea9ac 100644
--- a/trailer.c
+++ b/trailer.c
@@ -562,7 +562,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 		break;
 	default:
-		die("internal bug in trailer.c");
+		die("BUG: trailer.c: unhandled type %d", type);
 	}
 	return 0;
 }
diff --git a/transport.c b/transport.c
index 59b911e..5d446da 100644
--- a/transport.c
+++ b/transport.c
@@ -563,7 +563,7 @@ void transport_take_over(struct transport *transport,
 	struct git_transport_data *data;
 
 	if (!transport->smart_options)
-		die("Bug detected: Taking over transport requires non-NULL "
+		die("BUG: taking over transport requires non-NULL "
 		    "smart_options field.");
 
 	data = xcalloc(1, sizeof(*data));
diff --git a/wt-status.c b/wt-status.c
index de62ab2..3fb86a4 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die("bug: unhandled unmerged status %x", stagemask);
+		die("BUG: unhandled unmerged status %x", stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die("bug: unhandled diff status %c", status);
+		die("BUG: unhandled diff status %c", status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 03/16] Avoid translating bug messages
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
  2016-07-22 12:24       ` [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
  2016-07-22 12:24       ` [PATCH v4 02/16] Report bugs consistently Johannes Schindelin
@ 2016-07-22 12:24       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 04/16] merge-recursive: clarify code in was_tracked() Johannes Schindelin
                         ` (13 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:24 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

While working on the patch series that avoids die()ing in recursive
merges, the issue came up that bug reports (i.e. die("BUG: ...")
constructs) should never be translated, as the target audience is the
Git developer community, not necessarily the current user, and hence
a translated message would make it *harder* to address the problem.

So let's stop translating the obvious ones. As it is really, really
outside the purview of this patch series to see whether there are more
die() statements that report bugs and are currently translated, that
task is left for another day and patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 7400034..e0d5a57 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -958,7 +958,7 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 			if (!oid_eq(&a->oid, &b->oid))
 				result.clean = 0;
 		} else
-			die(_("BUG: unsupported object type in the tree"));
+			die("BUG: unsupported object type in the tree");
 	}
 
 	return result;
@@ -1802,7 +1802,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("BUG: fatal merge failure, shouldn't happen."));
+		die("BUG: fatal merge failure, shouldn't happen.");
 
 	return clean_merge;
 }
@@ -1860,7 +1860,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("BUG: unprocessed path??? %s"),
+				die("BUG: unprocessed path??? %s",
 				    entries->items[i].string);
 		}
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 04/16] merge-recursive: clarify code in was_tracked()
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (2 preceding siblings ...)
  2016-07-22 12:24       ` [PATCH v4 03/16] Avoid translating bug messages Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 05/16] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
                         ` (12 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It can be puzzling to see that was_tracked() asks to get an index entry
by name, but does not take a negative return value for an answer.

The reason we have to do this is that cache_name_pos() only looks for
entries in stage 0, even if nobody asked for any stage in particular.

Let's rewrite the logic a little bit, to handle the easy case early: if
cache_name_pos() returned a non-negative position, we know it is a match,
and we do not even have to compare the name again (cache_name_pos() did
that for us already). We can say right away: yes, this file was tracked.

Only if there was no exact match do we need to look harder for any
matching entry in stage 2.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 30 ++++++++++++++----------------
 1 file changed, 14 insertions(+), 16 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index e0d5a57..dc3182b 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -658,23 +658,21 @@ static int was_tracked(const char *path)
 {
 	int pos = cache_name_pos(path, strlen(path));
 
-	if (pos < 0)
-		pos = -1 - pos;
-	while (pos < active_nr &&
-	       !strcmp(path, active_cache[pos]->name)) {
-		/*
-		 * If stage #0, it is definitely tracked.
-		 * If it has stage #2 then it was tracked
-		 * before this merge started.  All other
-		 * cases the path was not tracked.
-		 */
-		switch (ce_stage(active_cache[pos])) {
-		case 0:
-		case 2:
+	if (0 <= pos)
+		/* we have been tracking this path */
+		return 1;
+
+	/*
+	 * Look for an unmerged entry for the path,
+	 * specifically stage #2, which would indicate
+	 * that "our" side before the merge started
+	 * had the path tracked (and resulted in a conflict).
+	 */
+	for (pos = -1 - pos;
+	     pos < active_nr && !strcmp(path, active_cache[pos]->name);
+	     pos++)
+		if (ce_stage(active_cache[pos]) == 2)
 			return 1;
-		}
-		pos++;
-	}
 	return 0;
 }
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 05/16] Prepare the builtins for a libified merge_recursive()
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (3 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 04/16] merge-recursive: clarify code in was_tracked() Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
                         ` (11 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Previously, callers of merge_trees() or merge_recursive() expected that
code to die() with an error message. This used to be okay because we
called those commands from scripts, and had a chance to print out a
message in case the command failed fatally (read: with exit code 128).

As scripting incurs its own set of problems (portability, speed,
idiosynchracies of different shells, limited data structures leading to
inefficient code), we are converting more and more of these scripts into
builtins, using library functions directly.

We already tried to use merge_recursive() directly in the builtin
git-am, for example. Unfortunately, we had to roll it back temporarily
because some of the code in merge-recursive.c still deemed it okay to
call die(), when the builtin am code really wanted to print out a useful
advice after the merge failed fatally. In the next commits, we want to
fix that.

The code touched by this commit expected merge_trees() to die() with
some useful message when there is an error condition, but merge_trees()
is going to be improved by converting all die() calls to return error()
instead (i.e. return value -1 after printing out the message as before),
so that the caller can react more flexibly.

This is a step to prepare for the version of merge_trees() that no
longer dies,  even if we just imitate the previous behavior by calling
exit(128): this is what callers of e.g. `git merge` have come to expect.

Note that the callers of the sequencer (revert and cherry-pick) already
fail fast even for the return value -1; The only difference is that they
now get a chance to say "<command> failed".

A caller of merge_trees() might want handle error messages themselves
(or even suppress them). As this patch is already complex enough, we
leave that change for a later patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 4 +++-
 builtin/merge.c    | 2 ++
 sequencer.c        | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 27c1a05..07dea3b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -567,8 +567,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.ancestor = old->name;
 			o.branch1 = new->name;
 			o.branch2 = "local";
-			merge_trees(&o, new->commit->tree, work,
+			ret = merge_trees(&o, new->commit->tree, work,
 				old->commit->tree, &result);
+			if (ret < 0)
+				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
 			if (ret)
diff --git a/builtin/merge.c b/builtin/merge.c
index 19b3bc2..148a9a5 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -673,6 +673,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 		hold_locked_index(&lock, 1);
 		clean = merge_recursive(&o, head,
 				remoteheads->item, reversed, &result);
+		if (clean < 0)
+			exit(128);
 		if (active_cache_changed &&
 		    write_locked_index(&the_index, &lock, COMMIT_LOCK))
 			die (_("unable to write %s"), get_index_file());
diff --git a/sequencer.c b/sequencer.c
index cdfac82..286a435 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	if (clean < 0)
+		return clean;
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
@@ -559,6 +561,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
 					 head, &msgbuf, opts);
+		if (res < 0)
+			return res;
 		write_message(&msgbuf, git_path_merge_msg());
 	} else {
 		struct commit_list *common = NULL;
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 06/16] merge_recursive: abort properly upon errors
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (4 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 05/16] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-25 22:09         ` Junio C Hamano
  2016-07-22 12:25       ` [PATCH v4 07/16] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
                         ` (10 subsequent siblings)
  16 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

There are a couple of places where return values indicating errors
are ignored. Let's teach them manners.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index dc3182b..2d4cb80 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1949,8 +1949,9 @@ int merge_recursive(struct merge_options *o,
 		saved_b2 = o->branch2;
 		o->branch1 = "Temporary merge branch 1";
 		o->branch2 = "Temporary merge branch 2";
-		merge_recursive(o, merged_common_ancestors, iter->item,
-				NULL, &merged_common_ancestors);
+		if (merge_recursive(o, merged_common_ancestors, iter->item,
+				    NULL, &merged_common_ancestors) < 0)
+			return -1;
 		o->branch1 = saved_b1;
 		o->branch2 = saved_b2;
 		o->call_depth--;
@@ -1966,6 +1967,8 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	if (clean < 0)
+		return clean;
 
 	if (o->call_depth) {
 		*result = make_virtual_commit(mrtree, "merged tree");
@@ -2022,6 +2025,9 @@ int merge_recursive_generic(struct merge_options *o,
 	hold_locked_index(lock, 1);
 	clean = merge_recursive(o, head_commit, next_commit, ca,
 			result);
+	if (clean < 0)
+		return clean;
+
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
 		return error(_("Unable to write index."));
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 07/16] merge-recursive: avoid returning a wholesale struct
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (5 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 08/16] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
                         ` (9 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It is technically allowed, as per C89, for functions' return type to
be complete structs (i.e. *not* just pointers to structs).

However, it was just an oversight of this developer when converting
Python code to C code in 6d297f8 (Status update on merge-recursive in
C, 2006-07-08) which introduced such a return type.

Besides, by converting this construct to pass in the struct, we can now
start returning a value that can indicate errors in future patches. This
will help the current effort to libify merge-recursive.c.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 106 ++++++++++++++++++++++++++++--------------------------
 1 file changed, 56 insertions(+), 50 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 2d4cb80..e1cea96 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -885,47 +885,47 @@ static int merge_3way(struct merge_options *o,
 	return merge_status;
 }
 
-static struct merge_file_info merge_file_1(struct merge_options *o,
+static int merge_file_1(struct merge_options *o,
 					   const struct diff_filespec *one,
 					   const struct diff_filespec *a,
 					   const struct diff_filespec *b,
 					   const char *branch1,
-					   const char *branch2)
+					   const char *branch2,
+					   struct merge_file_info *result)
 {
-	struct merge_file_info result;
-	result.merge = 0;
-	result.clean = 1;
+	result->merge = 0;
+	result->clean = 1;
 
 	if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-		result.clean = 0;
+		result->clean = 0;
 		if (S_ISREG(a->mode)) {
-			result.mode = a->mode;
-			oidcpy(&result.oid, &a->oid);
+			result->mode = a->mode;
+			oidcpy(&result->oid, &a->oid);
 		} else {
-			result.mode = b->mode;
-			oidcpy(&result.oid, &b->oid);
+			result->mode = b->mode;
+			oidcpy(&result->oid, &b->oid);
 		}
 	} else {
 		if (!oid_eq(&a->oid, &one->oid) && !oid_eq(&b->oid, &one->oid))
-			result.merge = 1;
+			result->merge = 1;
 
 		/*
 		 * Merge modes
 		 */
 		if (a->mode == b->mode || a->mode == one->mode)
-			result.mode = b->mode;
+			result->mode = b->mode;
 		else {
-			result.mode = a->mode;
+			result->mode = a->mode;
 			if (b->mode != one->mode) {
-				result.clean = 0;
-				result.merge = 1;
+				result->clean = 0;
+				result->merge = 1;
 			}
 		}
 
 		if (oid_eq(&a->oid, &b->oid) || oid_eq(&a->oid, &one->oid))
-			oidcpy(&result.oid, &b->oid);
+			oidcpy(&result->oid, &b->oid);
 		else if (oid_eq(&b->oid, &one->oid))
-			oidcpy(&result.oid, &a->oid);
+			oidcpy(&result->oid, &a->oid);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
 			int merge_status;
@@ -937,64 +937,66 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 				die(_("Failed to execute internal merge"));
 
 			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result.oid.hash))
+					    blob_type, result->oid.hash))
 				die(_("Unable to add %s to database"),
 				    a->path);
 
 			free(result_buf.ptr);
-			result.clean = (merge_status == 0);
+			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
-			result.clean = merge_submodule(result.oid.hash,
+			result->clean = merge_submodule(result->oid.hash,
 						       one->path,
 						       one->oid.hash,
 						       a->oid.hash,
 						       b->oid.hash,
 						       !o->call_depth);
 		} else if (S_ISLNK(a->mode)) {
-			oidcpy(&result.oid, &a->oid);
+			oidcpy(&result->oid, &a->oid);
 
 			if (!oid_eq(&a->oid, &b->oid))
-				result.clean = 0;
+				result->clean = 0;
 		} else
 			die("BUG: unsupported object type in the tree");
 	}
 
-	return result;
+	return 0;
 }
 
-static struct merge_file_info
-merge_file_special_markers(struct merge_options *o,
+static int merge_file_special_markers(struct merge_options *o,
 			   const struct diff_filespec *one,
 			   const struct diff_filespec *a,
 			   const struct diff_filespec *b,
 			   const char *branch1,
 			   const char *filename1,
 			   const char *branch2,
-			   const char *filename2)
+			   const char *filename2,
+			   struct merge_file_info *mfi)
 {
 	char *side1 = NULL;
 	char *side2 = NULL;
-	struct merge_file_info mfi;
+	int ret;
 
 	if (filename1)
 		side1 = xstrfmt("%s:%s", branch1, filename1);
 	if (filename2)
 		side2 = xstrfmt("%s:%s", branch2, filename2);
 
-	mfi = merge_file_1(o, one, a, b,
-			   side1 ? side1 : branch1, side2 ? side2 : branch2);
+	ret = merge_file_1(o, one, a, b,
+			   side1 ? side1 : branch1,
+			   side2 ? side2 : branch2, mfi);
 	free(side1);
 	free(side2);
-	return mfi;
+	return ret;
 }
 
-static struct merge_file_info merge_file_one(struct merge_options *o,
+static int merge_file_one(struct merge_options *o,
 					 const char *path,
 					 const struct object_id *o_oid, int o_mode,
 					 const struct object_id *a_oid, int a_mode,
 					 const struct object_id *b_oid, int b_mode,
 					 const char *branch1,
-					 const char *branch2)
+					 const char *branch2,
+					 struct merge_file_info *mfi)
 {
 	struct diff_filespec one, a, b;
 
@@ -1005,7 +1007,7 @@ static struct merge_file_info merge_file_one(struct merge_options *o,
 	a.mode = a_mode;
 	oidcpy(&b.oid, b_oid);
 	b.mode = b_mode;
-	return merge_file_1(o, &one, &a, &b, branch1, branch2);
+	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
 static void handle_change_delete(struct merge_options *o,
@@ -1178,11 +1180,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		struct merge_file_info mfi;
 		struct diff_filespec other;
 		struct diff_filespec *add;
-		mfi = merge_file_one(o, one->path,
+		if (merge_file_one(o, one->path,
 				 &one->oid, one->mode,
 				 &a->oid, a->mode,
 				 &b->oid, b->mode,
-				 ci->branch1, ci->branch2);
+				 ci->branch1, ci->branch2, &mfi))
+			return;
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
@@ -1236,12 +1239,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
-	mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
-					    o->branch1, c1->path,
-					    o->branch2, ci->ren1_other.path);
-	mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
-					    o->branch1, ci->ren2_other.path,
-					    o->branch2, c2->path);
+	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
+				       o->branch1, c1->path,
+				       o->branch2, ci->ren1_other.path, &mfi_c1) ||
+	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
+				       o->branch1, ci->ren2_other.path,
+				       o->branch2, c2->path, &mfi_c2))
+		return;
 
 	if (o->call_depth) {
 		/*
@@ -1464,12 +1468,13 @@ static int process_renames(struct merge_options *o,
 				       ren1_dst, branch2);
 				if (o->call_depth) {
 					struct merge_file_info mfi;
-					mfi = merge_file_one(o, ren1_dst, &null_oid, 0,
-							 &ren1->pair->two->oid,
-							 ren1->pair->two->mode,
-							 &dst_other.oid,
-							 dst_other.mode,
-							 branch1, branch2);
+					if (merge_file_one(o, ren1_dst, &null_oid, 0,
+							   &ren1->pair->two->oid,
+							   ren1->pair->two->mode,
+							   &dst_other.oid,
+							   dst_other.mode,
+							   branch1, branch2, &mfi))
+						return -1;
 					output(o, 1, _("Adding merged %s"), ren1_dst);
 					update_file(o, 0, &mfi.oid,
 						    mfi.mode, ren1_dst);
@@ -1627,9 +1632,10 @@ static int merge_content(struct merge_options *o,
 		if (dir_in_way(path, !o->call_depth))
 			df_conflict_remains = 1;
 	}
-	mfi = merge_file_special_markers(o, &one, &a, &b,
-					 o->branch1, path1,
-					 o->branch2, path2);
+	if (merge_file_special_markers(o, &one, &a, &b,
+				       o->branch1, path1,
+				       o->branch2, path2, &mfi))
+		return -1;
 
 	if (mfi.clean && !df_conflict_remains &&
 	    oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) {
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 08/16] merge-recursive: allow write_tree_from_memory() to error out
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (6 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 07/16] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 09/16] merge-recursive: handle return values indicating errors Johannes Schindelin
                         ` (8 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It is possible that a tree cannot be written (think: disk full). We
will want to give the caller a chance to clean up instead of letting
the program die() in such a case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index e1cea96..d5b9b1c 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1879,8 +1879,8 @@ int merge_trees(struct merge_options *o,
 	else
 		clean = 1;
 
-	if (o->call_depth)
-		*result = write_tree_from_memory(o);
+	if (o->call_depth && !(*result = write_tree_from_memory(o)))
+		return -1;
 
 	return clean;
 }
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 09/16] merge-recursive: handle return values indicating errors
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (7 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 08/16] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 10/16] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
                         ` (7 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

We are about to libify the recursive merge machinery, where we only
die() in case of a bug or memory contention. To that end, we must heed
negative return values as indicating errors.

This requires our functions to be careful to pass through error
conditions in call chains, and for quite a few functions this means
that they have to return values to begin with.

The next step will be to convert the places where we currently die() to
return negative values (read: -1) instead.

Note that we ignore errors reported by make_room_for_path(), consistent
with the previous behavior (update_file_flags() used the return value of
make_room_for_path() only to indicate an early return, but not a fatal
error): if the error is really a fatal error, we will notice later; If
not, it was not that serious a problem to begin with. (Witnesses in
favor of this reasoning are t4151-am-abort and t7610-mergetool, which
would start failing if we stopped on errors reported by
make_room_for_path()).

Also note: while this patch makes the code slightly less readable in
update_file_flags() (we introduce a new "goto free_buf;" instead of
an explicit "free(buf); return;"), it is a preparatory change for
the next patch where we will convert all of the die() calls in the same
function to go through the free_buf return path instead.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 252 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 150 insertions(+), 102 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index d5b9b1c..697ba03 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -733,12 +733,12 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	return error(msg, path, _(": perhaps a D/F conflict?"));
 }
 
-static void update_file_flags(struct merge_options *o,
-			      const struct object_id *oid,
-			      unsigned mode,
-			      const char *path,
-			      int update_cache,
-			      int update_wd)
+static int update_file_flags(struct merge_options *o,
+			     const struct object_id *oid,
+			     unsigned mode,
+			     const char *path,
+			     int update_cache,
+			     int update_wd)
 {
 	if (o->call_depth)
 		update_wd = 0;
@@ -774,8 +774,7 @@ static void update_file_flags(struct merge_options *o,
 
 		if (make_room_for_path(o, path) < 0) {
 			update_wd = 0;
-			free(buf);
-			goto update_index;
+			goto free_buf;
 		}
 		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
 			int fd;
@@ -798,20 +797,22 @@ static void update_file_flags(struct merge_options *o,
 		} else
 			die(_("do not know what to do with %06o %s '%s'"),
 			    mode, oid_to_hex(oid), path);
+ free_buf:
 		free(buf);
 	}
  update_index:
 	if (update_cache)
 		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+	return 0;
 }
 
-static void update_file(struct merge_options *o,
-			int clean,
-			const struct object_id *oid,
-			unsigned mode,
-			const char *path)
+static int update_file(struct merge_options *o,
+		       int clean,
+		       const struct object_id *oid,
+		       unsigned mode,
+		       const char *path)
 {
-	update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
+	return update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
 }
 
 /* Low level file merging, update and removal */
@@ -1010,7 +1011,7 @@ static int merge_file_one(struct merge_options *o,
 	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
-static void handle_change_delete(struct merge_options *o,
+static int handle_change_delete(struct merge_options *o,
 				 const char *path,
 				 const struct object_id *o_oid, int o_mode,
 				 const struct object_id *a_oid, int a_mode,
@@ -1018,6 +1019,7 @@ static void handle_change_delete(struct merge_options *o,
 				 const char *change, const char *change_past)
 {
 	char *renamed = NULL;
+	int ret = 0;
 	if (dir_in_way(path, !o->call_depth)) {
 		renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2);
 	}
@@ -1028,21 +1030,23 @@ static void handle_change_delete(struct merge_options *o,
 		 * correct; since there is no true "middle point" between
 		 * them, simply reuse the base version for virtual merge base.
 		 */
-		remove_file_from_cache(path);
-		update_file(o, 0, o_oid, o_mode, renamed ? renamed : path);
+		ret = remove_file_from_cache(path);
+		if (!ret)
+			ret = update_file(o, 0, o_oid, o_mode,
+					  renamed ? renamed : path);
 	} else if (!a_oid) {
 		if (!renamed) {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path);
-			update_file(o, 0, b_oid, b_mode, path);
+			ret = update_file(o, 0, b_oid, b_mode, path);
 		} else {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path, renamed);
-			update_file(o, 0, b_oid, b_mode, renamed);
+			ret = update_file(o, 0, b_oid, b_mode, renamed);
 		}
 	} else {
 		if (!renamed) {
@@ -1055,7 +1059,7 @@ static void handle_change_delete(struct merge_options *o,
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch2, change_past,
 			       o->branch1, o->branch1, path, renamed);
-			update_file(o, 0, a_oid, a_mode, renamed);
+			ret = update_file(o, 0, a_oid, a_mode, renamed);
 		}
 		/*
 		 * No need to call update_file() on path when !renamed, since
@@ -1065,9 +1069,11 @@ static void handle_change_delete(struct merge_options *o,
 		 */
 	}
 	free(renamed);
+
+	return ret;
 }
 
-static void conflict_rename_delete(struct merge_options *o,
+static int conflict_rename_delete(struct merge_options *o,
 				   struct diff_filepair *pair,
 				   const char *rename_branch,
 				   const char *other_branch)
@@ -1087,21 +1093,20 @@ static void conflict_rename_delete(struct merge_options *o,
 		b_mode = dest->mode;
 	}
 
-	handle_change_delete(o,
-			     o->call_depth ? orig->path : dest->path,
-			     &orig->oid, orig->mode,
-			     a_oid, a_mode,
-			     b_oid, b_mode,
-			     _("rename"), _("renamed"));
-
-	if (o->call_depth) {
-		remove_file_from_cache(dest->path);
-	} else {
-		update_stages(dest->path, NULL,
-			      rename_branch == o->branch1 ? dest : NULL,
-			      rename_branch == o->branch1 ? NULL : dest);
-	}
+	if (handle_change_delete(o,
+				 o->call_depth ? orig->path : dest->path,
+				 &orig->oid, orig->mode,
+				 a_oid, a_mode,
+				 b_oid, b_mode,
+				 _("rename"), _("renamed")))
+		return -1;
 
+	if (o->call_depth)
+		return remove_file_from_cache(dest->path);
+	else
+		return update_stages(dest->path, NULL,
+				     rename_branch == o->branch1 ? dest : NULL,
+				     rename_branch == o->branch1 ? NULL : dest);
 }
 
 static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
@@ -1117,7 +1122,7 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 	return target;
 }
 
-static void handle_file(struct merge_options *o,
+static int handle_file(struct merge_options *o,
 			struct diff_filespec *rename,
 			int stage,
 			struct rename_conflict_info *ci)
@@ -1127,6 +1132,7 @@ static void handle_file(struct merge_options *o,
 	const char *cur_branch, *other_branch;
 	struct diff_filespec other;
 	struct diff_filespec *add;
+	int ret;
 
 	if (stage == 2) {
 		dst_entry = ci->dst_entry1;
@@ -1141,7 +1147,8 @@ static void handle_file(struct merge_options *o,
 	add = filespec_from_entry(&other, dst_entry, stage ^ 1);
 	if (add) {
 		char *add_name = unique_path(o, rename->path, other_branch);
-		update_file(o, 0, &add->oid, add->mode, add_name);
+		if (update_file(o, 0, &add->oid, add->mode, add_name))
+			return -1;
 
 		remove_file(o, 0, rename->path, 0);
 		dst_name = unique_path(o, rename->path, cur_branch);
@@ -1152,17 +1159,20 @@ static void handle_file(struct merge_options *o,
 			       rename->path, other_branch, dst_name);
 		}
 	}
-	update_file(o, 0, &rename->oid, rename->mode, dst_name);
-	if (stage == 2)
-		update_stages(rename->path, NULL, rename, add);
+	if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
+		; /* fall through, do allow dst_name to be released */
+	else if (stage == 2)
+		ret = update_stages(rename->path, NULL, rename, add);
 	else
-		update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
+
+	return ret;
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
+static int conflict_rename_rename_1to2(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* One file was renamed in both branches, but to different names. */
@@ -1185,14 +1195,16 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 				 &a->oid, a->mode,
 				 &b->oid, b->mode,
 				 ci->branch1, ci->branch2, &mfi))
-			return;
+			return -1;
+
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
 		 * pathname and then either rename the add-source file to that
 		 * unique path, or use that unique path instead of src here.
 		 */
-		update_file(o, 0, &mfi.oid, mfi.mode, one->path);
+		if (update_file(o, 0, &mfi.oid, mfi.mode, one->path))
+			return -1;
 
 		/*
 		 * Above, we put the merged content at the merge-base's
@@ -1203,22 +1215,26 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * resolving the conflict at that path in its favor.
 		 */
 		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
-		if (add)
-			update_file(o, 0, &add->oid, add->mode, a->path);
+		if (add) {
+			if (update_file(o, 0, &add->oid, add->mode, a->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(a->path);
 		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
-		if (add)
-			update_file(o, 0, &add->oid, add->mode, b->path);
+		if (add) {
+			if (update_file(o, 0, &add->oid, add->mode, b->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(b->path);
-	} else {
-		handle_file(o, a, 2, ci);
-		handle_file(o, b, 3, ci);
-	}
+	} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
+		return -1;
+
+	return 0;
 }
 
-static void conflict_rename_rename_2to1(struct merge_options *o,
+static int conflict_rename_rename_2to1(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* Two files, a & b, were renamed to the same thing, c. */
@@ -1229,6 +1245,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	char *path = c1->path; /* == c2->path */
 	struct merge_file_info mfi_c1;
 	struct merge_file_info mfi_c2;
+	int ret;
 
 	output(o, 1, _("CONFLICT (rename/rename): "
 	       "Rename %s->%s in %s. "
@@ -1245,7 +1262,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 				       o->branch1, ci->ren2_other.path,
 				       o->branch2, c2->path, &mfi_c2))
-		return;
+		return -1;
 
 	if (o->call_depth) {
 		/*
@@ -1256,19 +1273,25 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 		 * again later for the non-recursive merge.
 		 */
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
-		update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, b->path);
+		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
+		if (!ret)
+			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
+					  b->path);
 	} else {
 		char *new_path1 = unique_path(o, path, ci->branch1);
 		char *new_path2 = unique_path(o, path, ci->branch2);
 		output(o, 1, _("Renaming %s to %s and %s to %s instead"),
 		       a->path, new_path1, b->path, new_path2);
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
-		update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, new_path2);
+		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
+		if (!ret)
+			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
+					  new_path2);
 		free(new_path2);
 		free(new_path1);
 	}
+
+	return ret;
 }
 
 static int process_renames(struct merge_options *o,
@@ -1453,12 +1476,13 @@ static int process_renames(struct merge_options *o,
 				 * update_file_flags() instead of
 				 * update_file().
 				 */
-				update_file_flags(o,
-						  &ren1->pair->two->oid,
-						  ren1->pair->two->mode,
-						  ren1_dst,
-						  1, /* update_cache */
-						  0  /* update_wd    */);
+				if (update_file_flags(o,
+						      &ren1->pair->two->oid,
+						      ren1->pair->two->mode,
+						      ren1_dst,
+						      1, /* update_cache */
+						      0  /* update_wd    */))
+					clean_merge = -1;
 			} else if (!oid_eq(&dst_other.oid, &null_oid)) {
 				clean_merge = 0;
 				try_merge = 1;
@@ -1473,22 +1497,28 @@ static int process_renames(struct merge_options *o,
 							   ren1->pair->two->mode,
 							   &dst_other.oid,
 							   dst_other.mode,
-							   branch1, branch2, &mfi))
-						return -1;
+							   branch1, branch2, &mfi)) {
+						clean_merge = -1;
+						goto cleanup_and_return;
+					}
 					output(o, 1, _("Adding merged %s"), ren1_dst);
-					update_file(o, 0, &mfi.oid,
-						    mfi.mode, ren1_dst);
+					if (update_file(o, 0, &mfi.oid,
+							mfi.mode, ren1_dst))
+						clean_merge = -1;
 					try_merge = 0;
 				} else {
 					char *new_path = unique_path(o, ren1_dst, branch2);
 					output(o, 1, _("Adding as %s instead"), new_path);
-					update_file(o, 0, &dst_other.oid,
-						    dst_other.mode, new_path);
+					if (update_file(o, 0, &dst_other.oid,
+							dst_other.mode, new_path))
+						clean_merge = -1;
 					free(new_path);
 				}
 			} else
 				try_merge = 1;
 
+			if (clean_merge < 0)
+				goto cleanup_and_return;
 			if (try_merge) {
 				struct diff_filespec *one, *a, *b;
 				src_other.path = (char *)ren1_src;
@@ -1515,6 +1545,7 @@ static int process_renames(struct merge_options *o,
 			}
 		}
 	}
+cleanup_and_return:
 	string_list_clear(&a_by_dst, 0);
 	string_list_clear(&b_by_dst, 0);
 
@@ -1577,18 +1608,18 @@ static int blob_unchanged(const struct object_id *o_oid,
 	return ret;
 }
 
-static void handle_modify_delete(struct merge_options *o,
+static int handle_modify_delete(struct merge_options *o,
 				 const char *path,
 				 struct object_id *o_oid, int o_mode,
 				 struct object_id *a_oid, int a_mode,
 				 struct object_id *b_oid, int b_mode)
 {
-	handle_change_delete(o,
-			     path,
-			     o_oid, o_mode,
-			     a_oid, a_mode,
-			     b_oid, b_mode,
-			     _("modify"), _("modified"));
+	return handle_change_delete(o,
+				    path,
+				    o_oid, o_mode,
+				    a_oid, a_mode,
+				    b_oid, b_mode,
+				    _("modify"), _("modified"));
 }
 
 static int merge_content(struct merge_options *o,
@@ -1662,7 +1693,8 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			update_stages(path, &one, &a, &b);
+			if (update_stages(path, &one, &a, &b))
+				return -1;
 	}
 
 	if (df_conflict_remains) {
@@ -1670,30 +1702,33 @@ static int merge_content(struct merge_options *o,
 		if (o->call_depth) {
 			remove_file_from_cache(path);
 		} else {
-			if (!mfi.clean)
-				update_stages(path, &one, &a, &b);
-			else {
+			if (!mfi.clean) {
+				if (update_stages(path, &one, &a, &b))
+					return -1;
+			} else {
 				int file_from_stage2 = was_tracked(path);
 				struct diff_filespec merged;
 				oidcpy(&merged.oid, &mfi.oid);
 				merged.mode = mfi.mode;
 
-				update_stages(path, NULL,
-					      file_from_stage2 ? &merged : NULL,
-					      file_from_stage2 ? NULL : &merged);
+				if (update_stages(path, NULL,
+						  file_from_stage2 ? &merged : NULL,
+						  file_from_stage2 ? NULL : &merged))
+					return -1;
 			}
 
 		}
 		new_path = unique_path(o, path, rename_conflict_info->branch1);
 		output(o, 1, _("Adding as %s instead"), new_path);
-		update_file(o, 0, &mfi.oid, mfi.mode, new_path);
+		if (update_file(o, 0, &mfi.oid, mfi.mode, new_path)) {
+			free(new_path);
+			return -1;
+		}
 		free(new_path);
 		mfi.clean = 0;
-	} else {
-		update_file(o, mfi.clean, &mfi.oid, mfi.mode, path);
-	}
+	} else if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, path))
+		return -1;
 	return mfi.clean;
-
 }
 
 /* Per entry merge function */
@@ -1721,17 +1756,21 @@ static int process_entry(struct merge_options *o,
 			break;
 		case RENAME_DELETE:
 			clean_merge = 0;
-			conflict_rename_delete(o, conflict_info->pair1,
-					       conflict_info->branch1,
-					       conflict_info->branch2);
+			if (conflict_rename_delete(o,
+						   conflict_info->pair1,
+						   conflict_info->branch1,
+						   conflict_info->branch2))
+				clean_merge = -1;
 			break;
 		case RENAME_ONE_FILE_TO_TWO:
 			clean_merge = 0;
-			conflict_rename_rename_1to2(o, conflict_info);
+			if (conflict_rename_rename_1to2(o, conflict_info))
+				clean_merge = -1;
 			break;
 		case RENAME_TWO_FILES_TO_ONE:
 			clean_merge = 0;
-			conflict_rename_rename_2to1(o, conflict_info);
+			if (conflict_rename_rename_2to1(o, conflict_info))
+				clean_merge = -1;
 			break;
 		default:
 			entry->processed = 0;
@@ -1751,8 +1790,9 @@ static int process_entry(struct merge_options *o,
 		} else {
 			/* Modify/delete; deleted side may have put a directory in the way */
 			clean_merge = 0;
-			handle_modify_delete(o, path, o_oid, o_mode,
-					     a_oid, a_mode, b_oid, b_mode);
+			if (handle_modify_delete(o, path, o_oid, o_mode,
+						 a_oid, a_mode, b_oid, b_mode))
+				clean_merge = -1;
 		}
 	} else if ((!o_oid && a_oid && !b_oid) ||
 		   (!o_oid && !a_oid && b_oid)) {
@@ -1784,14 +1824,16 @@ static int process_entry(struct merge_options *o,
 			output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
 			       "Adding %s as %s"),
 			       conf, path, other_branch, path, new_path);
-			update_file(o, 0, oid, mode, new_path);
-			if (o->call_depth)
+			if (update_file(o, 0, oid, mode, new_path))
+				clean_merge = -1;
+			else if (o->call_depth)
 				remove_file_from_cache(path);
 			free(new_path);
 		} else {
 			output(o, 2, _("Adding %s"), path);
 			/* do not overwrite file if already present */
-			update_file_flags(o, oid, mode, path, 1, !a_oid);
+			if (update_file_flags(o, oid, mode, path, 1, !a_oid))
+				clean_merge = -1;
 		}
 	} else if (a_oid && b_oid) {
 		/* Case C: Added in both (check for same permissions) and */
@@ -1854,12 +1896,18 @@ int merge_trees(struct merge_options *o,
 		re_head  = get_renames(o, head, common, head, merge, entries);
 		re_merge = get_renames(o, merge, common, head, merge, entries);
 		clean = process_renames(o, re_head, re_merge);
+		if (clean < 0)
+			return clean;
 		for (i = entries->nr-1; 0 <= i; i--) {
 			const char *path = entries->items[i].string;
 			struct stage_data *e = entries->items[i].util;
-			if (!e->processed
-				&& !process_entry(o, path, e))
-				clean = 0;
+			if (!e->processed) {
+				int ret = process_entry(o, path, e);
+				if (!ret)
+					clean = 0;
+				else if (ret < 0)
+					return ret;
+			}
 		}
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 10/16] merge-recursive: switch to returning errors instead of dying
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (8 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 09/16] merge-recursive: handle return values indicating errors Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
                         ` (6 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The recursive merge machinery is supposed to be a library function, i.e.
it should return an error when it fails. Originally the functions were
part of the builtin "merge-recursive", though, where it was simpler to
call die() and be done with error handling.

The existing callers were already prepared to detect negative return
values to indicate errors and to behave as previously: exit with code 128
(which is the same thing that die() does, after printing the message).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 62 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 35 insertions(+), 27 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 697ba03..24b42d6 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -266,8 +266,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 		active_cache_tree = cache_tree();
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
-	    cache_tree_update(&the_index, 0) < 0)
-		die(_("error building trees"));
+	    cache_tree_update(&the_index, 0) < 0) {
+		error(_("error building trees"));
+		return NULL;
+	}
 
 	result = lookup_tree(active_cache_tree->sha1);
 
@@ -707,12 +709,10 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	/* Make sure leading directories are created */
 	status = safe_create_leading_directories_const(path);
 	if (status) {
-		if (status == SCLD_EXISTS) {
+		if (status == SCLD_EXISTS)
 			/* something else exists */
-			error(msg, path, _(": perhaps a D/F conflict?"));
-			return -1;
-		}
-		die(msg, path, "");
+			return error(msg, path, _(": perhaps a D/F conflict?"));
+		return error(msg, path, "");
 	}
 
 	/*
@@ -740,6 +740,8 @@ static int update_file_flags(struct merge_options *o,
 			     int update_cache,
 			     int update_wd)
 {
+	int ret = 0;
+
 	if (o->call_depth)
 		update_wd = 0;
 
@@ -760,9 +762,11 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(oid->hash, &type, &size);
 		if (!buf)
-			die(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
-		if (type != OBJ_BLOB)
-			die(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			return error(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
+		if (type != OBJ_BLOB) {
+			ret = error(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			goto free_buf;
+		}
 		if (S_ISREG(mode)) {
 			struct strbuf strbuf = STRBUF_INIT;
 			if (convert_to_working_tree(path, buf, size, &strbuf)) {
@@ -783,8 +787,11 @@ static int update_file_flags(struct merge_options *o,
 			else
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-			if (fd < 0)
-				die_errno(_("failed to open '%s'"), path);
+			if (fd < 0) {
+				ret = error_errno(_("failed to open '%s'"),
+						  path);
+				goto free_buf;
+			}
 			write_in_full(fd, buf, size);
 			close(fd);
 		} else if (S_ISLNK(mode)) {
@@ -792,18 +799,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				die_errno(_("failed to symlink '%s'"), path);
+				ret = error_errno(_("failed to symlink '%s'"), path);
 			free(lnk);
 		} else
-			die(_("do not know what to do with %06o %s '%s'"),
-			    mode, oid_to_hex(oid), path);
+			ret = error(_("do not know what to do with %06o %s '%s'"),
+				    mode, oid_to_hex(oid), path);
  free_buf:
 		free(buf);
 	}
  update_index:
-	if (update_cache)
+	if (!ret && update_cache)
 		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-	return 0;
+	return ret;
 }
 
 static int update_file(struct merge_options *o,
@@ -929,20 +936,22 @@ static int merge_file_1(struct merge_options *o,
 			oidcpy(&result->oid, &a->oid);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
-			int merge_status;
+			int ret = 0, merge_status;
 
 			merge_status = merge_3way(o, &result_buf, one, a, b,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				die(_("Failed to execute internal merge"));
+				ret = error(_("Failed to execute internal merge"));
 
-			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result->oid.hash))
-				die(_("Unable to add %s to database"),
-				    a->path);
+			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
+						    blob_type, result->oid.hash))
+				ret = error(_("Unable to add %s to database"),
+					    a->path);
 
 			free(result_buf.ptr);
+			if (ret)
+				return ret;
 			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
 			result->clean = merge_submodule(result->oid.hash,
@@ -1876,11 +1885,10 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			die(_("merging of trees %s and %s failed"),
+			error(_("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
-		else
-			exit(128);
+		return -1;
 	}
 
 	if (unmerged_cache()) {
@@ -2011,7 +2019,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			die(_("merge returned no commit"));
+			return error(_("merge returned no commit"));
 	}
 
 	discard_cache();
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 11/16] am -3: use merge_recursive() directly again
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (9 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 10/16] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-25 22:19         ` Junio C Hamano
  2016-07-22 12:25       ` [PATCH v4 12/16] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
                         ` (5 subsequent siblings)
  16 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Last October, we had to change this code to run `git merge-recursive`
in a child process: git-am wants to print some helpful advice when the
merge failed, but the code in question was not prepared to return, it
die()d instead.

We are finally at a point when the code *is* prepared to return errors,
and can avoid the child process again.

This reverts commit c63d4b2 (am -3: do not let failed merge from
completing the error codepath, 2015-10-09), with the necessary changes
to adjust for the fact that Git's source code changed in the meantime
(such as: using OIDs instead of hashes in the recursive merge, and a
removed gender bias).

Note: the code now calls merge_recursive_generic() again. Unlike
merge_trees() and merge_recursive(), this function returns 0 upon success,
as most of Git's functions. Therefore, the error value -1 naturally is
handled correctly, and we do not have to take care of it specifically.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/am.c | 62 +++++++++++++++++++++---------------------------------------
 1 file changed, 22 insertions(+), 40 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index b77bf11..cfb79ea 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1579,47 +1579,18 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 }
 
 /**
- * Do the three-way merge using fake ancestor, their tree constructed
- * from the fake ancestor and the postimage of the patch, and our
- * state.
- */
-static int run_fallback_merge_recursive(const struct am_state *state,
-					unsigned char *orig_tree,
-					unsigned char *our_tree,
-					unsigned char *their_tree)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-	int status;
-
-	cp.git_cmd = 1;
-
-	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
-			 sha1_to_hex(their_tree), linelen(state->msg), state->msg);
-	if (state->quiet)
-		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
-
-	argv_array_push(&cp.args, "merge-recursive");
-	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
-	argv_array_push(&cp.args, "--");
-	argv_array_push(&cp.args, sha1_to_hex(our_tree));
-	argv_array_push(&cp.args, sha1_to_hex(their_tree));
-
-	status = run_command(&cp) ? (-1) : 0;
-	discard_cache();
-	read_cache();
-	return status;
-}
-
-/**
  * Attempt a threeway merge, using index_path as the temporary index.
  */
 static int fall_back_threeway(const struct am_state *state, const char *index_path)
 {
-	unsigned char orig_tree[GIT_SHA1_RAWSZ], their_tree[GIT_SHA1_RAWSZ],
-		      our_tree[GIT_SHA1_RAWSZ];
+	struct object_id orig_tree, their_tree, our_tree;
+	const struct object_id *bases[1] = { &orig_tree };
+	struct merge_options o;
+	struct commit *result;
+	char *their_tree_name;
 
-	if (get_sha1("HEAD", our_tree) < 0)
-		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
+	if (get_oid("HEAD", &our_tree) < 0)
+		hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
 
 	if (build_fake_ancestor(state, index_path))
 		return error("could not build fake ancestor");
@@ -1627,7 +1598,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	discard_cache();
 	read_cache_from(index_path);
 
-	if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
 		return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
 
 	say(state, stdout, _("Using index info to reconstruct a base tree..."));
@@ -1643,7 +1614,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		init_revisions(&rev_info, NULL);
 		rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
 		diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
-		add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
+		add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
 		diff_setup_done(&rev_info.diffopt);
 		run_diff_index(&rev_info, 1);
 	}
@@ -1652,7 +1623,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		return error(_("Did you hand edit your patch?\n"
 				"It does not apply to blobs recorded in its index."));
 
-	if (write_index_as_tree(their_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(their_tree.hash, &the_index, index_path, 0, NULL))
 		return error("could not write tree");
 
 	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
@@ -1668,11 +1639,22 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	 * changes.
 	 */
 
-	if (run_fallback_merge_recursive(state, orig_tree, our_tree, their_tree)) {
+	init_merge_options(&o);
+
+	o.branch1 = "HEAD";
+	their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+	o.branch2 = their_tree_name;
+
+	if (state->quiet)
+		o.verbosity = 0;
+
+	if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
 		rerere(state->allow_rerere_autoupdate);
+		free(their_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
 
+	free(their_tree_name);
 	return 0;
 }
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 12/16] merge-recursive: flush output buffer before printing error messages
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (10 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 13/16] merge-recursive: write the commit title in one go Johannes Schindelin
                         ` (4 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The data structure passed to the recursive merge machinery has a feature
where the caller can ask for the output to be buffered into a strbuf, by
setting the field 'buffer_output'.

Previously, we simply swallowed the buffered output when showing error
messages. With this patch, we show the output first, and only then print
the error message.

Currently, the only user of that buffering is merge_recursive() itself,
to avoid the progress output to interfere.

In the next patches, we will introduce a new buffer_output mode that
forces merge_recursive() to retain the output buffer for further
processing by the caller. If the caller asked for that, we will then
also write the error messages into the output buffer. This is necessary
to give the caller more control not only how to react in case of errors
but also control how/if to display the error messages.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 116 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 68 insertions(+), 48 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 24b42d6..3ef1e2f 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -23,6 +23,28 @@
 #include "dir.h"
 #include "submodule.h"
 
+static void flush_output(struct merge_options *o)
+{
+	if (o->obuf.len) {
+		fputs(o->obuf.buf, stdout);
+		strbuf_reset(&o->obuf);
+	}
+}
+
+static int err(struct merge_options *o, const char *err, ...)
+{
+	va_list params;
+
+	va_start(params, err);
+	flush_output(o);
+	strbuf_vaddf(&o->obuf, err, params);
+	error("%s", o->obuf.buf);
+	strbuf_reset(&o->obuf);
+	va_end(params);
+
+	return -1;
+}
+
 static struct tree *shift_tree_object(struct tree *one, struct tree *two,
 				      const char *subtree_shift)
 {
@@ -148,14 +170,6 @@ static int show(struct merge_options *o, int v)
 	return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
 }
 
-static void flush_output(struct merge_options *o)
-{
-	if (o->obuf.len) {
-		fputs(o->obuf.buf, stdout);
-		strbuf_reset(&o->obuf);
-	}
-}
-
 __attribute__((format (printf, 3, 4)))
 static void output(struct merge_options *o, int v, const char *fmt, ...)
 {
@@ -198,7 +212,8 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
 	}
 }
 
-static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
+static int add_cacheinfo(struct merge_options *o,
+		unsigned int mode, const struct object_id *oid,
 		const char *path, int stage, int refresh, int options)
 {
 	struct cache_entry *ce;
@@ -206,7 +221,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
 			      (refresh ? (CE_MATCH_REFRESH |
 					  CE_MATCH_IGNORE_MISSING) : 0 ));
 	if (!ce)
-		return error(_("addinfo_cache failed for path '%s'"), path);
+		return err(o, _("addinfo_cache failed for path '%s'"), path);
 	return add_cache_entry(ce, options);
 }
 
@@ -267,7 +282,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
 	    cache_tree_update(&the_index, 0) < 0) {
-		error(_("error building trees"));
+		err(o, _("error building trees"));
 		return NULL;
 	}
 
@@ -535,7 +550,8 @@ static struct string_list *get_renames(struct merge_options *o,
 	return renames;
 }
 
-static int update_stages(const char *path, const struct diff_filespec *o,
+static int update_stages(struct merge_options *opt, const char *path,
+			 const struct diff_filespec *o,
 			 const struct diff_filespec *a,
 			 const struct diff_filespec *b)
 {
@@ -554,13 +570,13 @@ static int update_stages(const char *path, const struct diff_filespec *o,
 		if (remove_file_from_cache(path))
 			return -1;
 	if (o)
-		if (add_cacheinfo(o->mode, &o->oid, path, 1, 0, options))
+		if (add_cacheinfo(opt, o->mode, &o->oid, path, 1, 0, options))
 			return -1;
 	if (a)
-		if (add_cacheinfo(a->mode, &a->oid, path, 2, 0, options))
+		if (add_cacheinfo(opt, a->mode, &a->oid, path, 2, 0, options))
 			return -1;
 	if (b)
-		if (add_cacheinfo(b->mode, &b->oid, path, 3, 0, options))
+		if (add_cacheinfo(opt, b->mode, &b->oid, path, 3, 0, options))
 			return -1;
 	return 0;
 }
@@ -711,8 +727,8 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	if (status) {
 		if (status == SCLD_EXISTS)
 			/* something else exists */
-			return error(msg, path, _(": perhaps a D/F conflict?"));
-		return error(msg, path, "");
+			return err(o, msg, path, _(": perhaps a D/F conflict?"));
+		return err(o, msg, path, "");
 	}
 
 	/*
@@ -720,7 +736,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	 * tracking it.
 	 */
 	if (would_lose_untracked(path))
-		return error(_("refusing to lose untracked file at '%s'"),
+		return err(o, _("refusing to lose untracked file at '%s'"),
 			     path);
 
 	/* Successful unlink is good.. */
@@ -730,7 +746,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	if (errno == ENOENT)
 		return 0;
 	/* .. but not some other error (who really cares what?) */
-	return error(msg, path, _(": perhaps a D/F conflict?"));
+	return err(o, msg, path, _(": perhaps a D/F conflict?"));
 }
 
 static int update_file_flags(struct merge_options *o,
@@ -762,9 +778,9 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(oid->hash, &type, &size);
 		if (!buf)
-			return error(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
+			return err(o, _("cannot read object %s '%s'"), oid_to_hex(oid), path);
 		if (type != OBJ_BLOB) {
-			ret = error(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			ret = err(o, _("blob expected for %s '%s'"), oid_to_hex(oid), path);
 			goto free_buf;
 		}
 		if (S_ISREG(mode)) {
@@ -788,8 +804,8 @@ static int update_file_flags(struct merge_options *o,
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
 			if (fd < 0) {
-				ret = error_errno(_("failed to open '%s'"),
-						  path);
+				ret = err(o, _("failed to open '%s': %s"),
+					  path, strerror(errno));
 				goto free_buf;
 			}
 			write_in_full(fd, buf, size);
@@ -799,17 +815,19 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				ret = error_errno(_("failed to symlink '%s'"), path);
+				ret = err(o, _("failed to symlink '%s': %s"),
+					path, strerror(errno));
 			free(lnk);
 		} else
-			ret = error(_("do not know what to do with %06o %s '%s'"),
-				    mode, oid_to_hex(oid), path);
+			ret = err(o,
+				  _("do not know what to do with %06o %s '%s'"),
+				  mode, oid_to_hex(oid), path);
  free_buf:
 		free(buf);
 	}
  update_index:
 	if (!ret && update_cache)
-		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+		add_cacheinfo(o, mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 	return ret;
 }
 
@@ -942,12 +960,12 @@ static int merge_file_1(struct merge_options *o,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				ret = error(_("Failed to execute internal merge"));
+				ret = err(o, _("Failed to execute internal merge"));
 
 			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
 						    blob_type, result->oid.hash))
-				ret = error(_("Unable to add %s to database"),
-					    a->path);
+				ret = err(o, _("Unable to add %s to database"),
+					  a->path);
 
 			free(result_buf.ptr);
 			if (ret)
@@ -1113,7 +1131,7 @@ static int conflict_rename_delete(struct merge_options *o,
 	if (o->call_depth)
 		return remove_file_from_cache(dest->path);
 	else
-		return update_stages(dest->path, NULL,
+		return update_stages(o, dest->path, NULL,
 				     rename_branch == o->branch1 ? dest : NULL,
 				     rename_branch == o->branch1 ? NULL : dest);
 }
@@ -1171,9 +1189,9 @@ static int handle_file(struct merge_options *o,
 	if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
 		; /* fall through, do allow dst_name to be released */
 	else if (stage == 2)
-		ret = update_stages(rename->path, NULL, rename, add);
+		ret = update_stages(o, rename->path, NULL, rename, add);
 	else
-		ret = update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(o, rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
@@ -1566,23 +1584,25 @@ static struct object_id *stage_oid(const struct object_id *oid, unsigned mode)
 	return (is_null_oid(oid) || mode == 0) ? NULL: (struct object_id *)oid;
 }
 
-static int read_oid_strbuf(const struct object_id *oid, struct strbuf *dst)
+static int read_oid_strbuf(struct merge_options *o,
+	const struct object_id *oid, struct strbuf *dst)
 {
 	void *buf;
 	enum object_type type;
 	unsigned long size;
 	buf = read_sha1_file(oid->hash, &type, &size);
 	if (!buf)
-		return error(_("cannot read object %s"), oid_to_hex(oid));
+		return err(o, _("cannot read object %s"), oid_to_hex(oid));
 	if (type != OBJ_BLOB) {
 		free(buf);
-		return error(_("object %s is not a blob"), oid_to_hex(oid));
+		return err(o, _("object %s is not a blob"), oid_to_hex(oid));
 	}
 	strbuf_attach(dst, buf, size, size + 1);
 	return 0;
 }
 
-static int blob_unchanged(const struct object_id *o_oid,
+static int blob_unchanged(struct merge_options *opt,
+			  const struct object_id *o_oid,
 			  unsigned o_mode,
 			  const struct object_id *a_oid,
 			  unsigned a_mode,
@@ -1600,7 +1620,7 @@ static int blob_unchanged(const struct object_id *o_oid,
 		return 0;
 
 	assert(o_oid && a_oid);
-	if (read_oid_strbuf(o_oid, &o) || read_oid_strbuf(a_oid, &a))
+	if (read_oid_strbuf(opt, o_oid, &o) || read_oid_strbuf(opt, a_oid, &a))
 		goto error_return;
 	/*
 	 * Note: binary | is used so that both renormalizations are
@@ -1689,7 +1709,7 @@ static int merge_content(struct merge_options *o,
 		 */
 		path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
 		if (!path_renamed_outside_HEAD) {
-			add_cacheinfo(mfi.mode, &mfi.oid, path,
+			add_cacheinfo(o, mfi.mode, &mfi.oid, path,
 				      0, (!o->call_depth), 0);
 			return mfi.clean;
 		}
@@ -1702,7 +1722,7 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			if (update_stages(path, &one, &a, &b))
+			if (update_stages(o, path, &one, &a, &b))
 				return -1;
 	}
 
@@ -1712,7 +1732,7 @@ static int merge_content(struct merge_options *o,
 			remove_file_from_cache(path);
 		} else {
 			if (!mfi.clean) {
-				if (update_stages(path, &one, &a, &b))
+				if (update_stages(o, path, &one, &a, &b))
 					return -1;
 			} else {
 				int file_from_stage2 = was_tracked(path);
@@ -1720,7 +1740,7 @@ static int merge_content(struct merge_options *o,
 				oidcpy(&merged.oid, &mfi.oid);
 				merged.mode = mfi.mode;
 
-				if (update_stages(path, NULL,
+				if (update_stages(o, path, NULL,
 						  file_from_stage2 ? &merged : NULL,
 						  file_from_stage2 ? NULL : &merged))
 					return -1;
@@ -1788,8 +1808,8 @@ static int process_entry(struct merge_options *o,
 	} else if (o_oid && (!a_oid || !b_oid)) {
 		/* Case A: Deleted in one */
 		if ((!a_oid && !b_oid) ||
-		    (!b_oid && blob_unchanged(o_oid, o_mode, a_oid, a_mode, normalize, path)) ||
-		    (!a_oid && blob_unchanged(o_oid, o_mode, b_oid, b_mode, normalize, path))) {
+		    (!b_oid && blob_unchanged(o, o_oid, o_mode, a_oid, a_mode, normalize, path)) ||
+		    (!a_oid && blob_unchanged(o, o_oid, o_mode, b_oid, b_mode, normalize, path))) {
 			/* Deleted in both or deleted in one and
 			 * unchanged in the other */
 			if (a_oid)
@@ -1885,7 +1905,7 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			error(_("merging of trees %s and %s failed"),
+			err(o, _("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
 		return -1;
@@ -2019,7 +2039,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			return error(_("merge returned no commit"));
+			return err(o, _("merge returned no commit"));
 	}
 
 	discard_cache();
@@ -2078,7 +2098,7 @@ int merge_recursive_generic(struct merge_options *o,
 		for (i = 0; i < num_base_list; ++i) {
 			struct commit *base;
 			if (!(base = get_ref(base_list[i], oid_to_hex(base_list[i]))))
-				return error(_("Could not parse object '%s'"),
+				return err(o, _("Could not parse object '%s'"),
 					oid_to_hex(base_list[i]));
 			commit_list_insert(base, &ca);
 		}
@@ -2092,7 +2112,7 @@ int merge_recursive_generic(struct merge_options *o,
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
-		return error(_("Unable to write index."));
+		return err(o, _("Unable to write index."));
 
 	return clean ? 0 : 1;
 }
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 13/16] merge-recursive: write the commit title in one go
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (11 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 12/16] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 14/16] merge-recursive: offer an option to retain the output in 'obuf' Johannes Schindelin
                         ` (3 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

In 66a155b (Enable output buffering in merge-recursive., 2007-01-14), we
changed the code such that it prints the output in one go, to avoid
interfering with the progress output.

Let's make sure that the same holds true when outputting the commit
title: previously, we used several printf() statements to stdout and
speculated that stdout's buffer is large enough to hold the entire
commit title.

Apart from making that speculation unnecessary, we change the code to
add the message to the output buffer before flushing for another reason:
the next commit will introduce a new level of output buffering, where
the caller can request the output not to be flushed, but to be retained
for further processing.

This latter feature will be needed when teaching the sequencer to do
rebase -i's brunt work: it wants to control the output of the
cherry-picks (i.e. recursive merges).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 3ef1e2f..2bc41fe 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -191,25 +191,26 @@ static void output(struct merge_options *o, int v, const char *fmt, ...)
 
 static void output_commit_title(struct merge_options *o, struct commit *commit)
 {
-	int i;
-	flush_output(o);
-	for (i = o->call_depth; i--;)
-		fputs("  ", stdout);
+	strbuf_addchars(&o->obuf, ' ', o->call_depth * 2);
 	if (commit->util)
-		printf("virtual %s\n", merge_remote_util(commit)->name);
+		strbuf_addf(&o->obuf, "virtual %s\n",
+			merge_remote_util(commit)->name);
 	else {
-		printf("%s ", find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
+		strbuf_addf(&o->obuf, "%s ",
+			find_unique_abbrev(commit->object.oid.hash,
+				DEFAULT_ABBREV));
 		if (parse_commit(commit) != 0)
-			printf(_("(bad commit)\n"));
+			strbuf_addf(&o->obuf, _("(bad commit)\n"));
 		else {
 			const char *title;
 			const char *msg = get_commit_buffer(commit, NULL);
 			int len = find_commit_subject(msg, &title);
 			if (len)
-				printf("%.*s\n", len, title);
+				strbuf_addf(&o->obuf, "%.*s\n", len, title);
 			unuse_commit_buffer(commit, msg);
 		}
 	}
+	flush_output(o);
 }
 
 static int add_cacheinfo(struct merge_options *o,
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 14/16] merge-recursive: offer an option to retain the output in 'obuf'
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (12 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 13/16] merge-recursive: write the commit title in one go Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:25       ` [PATCH v4 15/16] Ensure that the output buffer is released after calling merge_trees() Johannes Schindelin
                         ` (2 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Since 66a155b (Enable output buffering in merge-recursive., 2007-01-14),
we already accumulate the output in a buffer. The idea was to avoid
interfering with the progress output that goes to stderr, which is
unbuffered, when we write to stdout, which is buffered.

We extend that buffering to allow the caller to handle the output
(possibly suppressing it). This will help us when extending the
sequencer to do rebase -i's brunt work: it does not want the picks to
print anything by default but instead determine itself whether to print
the output or not.

Note that we also redirect the error messages into the output buffer
when the caller asked not to flush the output buffer, for two reasons:
1) to retain the correct output order, and 2) to allow the caller to
suppress *all* output.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 +++++++++++++----
 merge-recursive.h |  2 +-
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 2bc41fe..1746c38 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -25,7 +25,7 @@
 
 static void flush_output(struct merge_options *o)
 {
-	if (o->obuf.len) {
+	if (o->buffer_output < 2 && o->obuf.len) {
 		fputs(o->obuf.buf, stdout);
 		strbuf_reset(&o->obuf);
 	}
@@ -36,10 +36,19 @@ static int err(struct merge_options *o, const char *err, ...)
 	va_list params;
 
 	va_start(params, err);
-	flush_output(o);
+	if (o->buffer_output < 2)
+		flush_output(o);
+	else {
+		strbuf_complete(&o->obuf, '\n');
+		strbuf_addstr(&o->obuf, "error: ");
+	}
 	strbuf_vaddf(&o->obuf, err, params);
-	error("%s", o->obuf.buf);
-	strbuf_reset(&o->obuf);
+	if (o->buffer_output > 1)
+		strbuf_addch(&o->obuf, '\n');
+	else {
+		error("%s", o->obuf.buf);
+		strbuf_reset(&o->obuf);
+	}
 	va_end(params);
 
 	return -1;
diff --git a/merge-recursive.h b/merge-recursive.h
index d415724..340704c 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -13,7 +13,7 @@ struct merge_options {
 		MERGE_RECURSIVE_THEIRS
 	} recursive_variant;
 	const char *subtree_shift;
-	unsigned buffer_output : 1;
+	unsigned buffer_output : 2; /* 1: output at end, 2: keep buffered */
 	unsigned renormalize : 1;
 	long xdl_opts;
 	int verbosity;
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 15/16] Ensure that the output buffer is released after calling merge_trees()
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (13 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 14/16] merge-recursive: offer an option to retain the output in 'obuf' Johannes Schindelin
@ 2016-07-22 12:25       ` Johannes Schindelin
  2016-07-22 12:26       ` [PATCH v4 16/16] merge-recursive: flush output buffer even when erroring out Johannes Schindelin
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The recursive merge machinery accumulates its output in an output
buffer, to be flushed at the end of merge_recursive(). At this point,
we forgot to release the output buffer.

When calling merge_trees() (i.e. the non-recursive part of the recursive
merge) directly, the output buffer is never flushed because the caller
may be merge_recursive() which wants to flush the output itself.

For the same reason, merge_trees() cannot release the output buffer: it
may still be needed.

Forgetting to release the output buffer did not matter much when running
git-checkout, or git-merge-recursive, because we exited after the
operation anyway. Ever since cherry-pick learned to pick a commit range,
however, this memory leak had the potential of becoming a problem.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 1 +
 merge-recursive.c  | 2 ++
 sequencer.c        | 1 +
 3 files changed, 4 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 07dea3b..8d852d4 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -573,6 +573,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
+			strbuf_release(&o.obuf);
 			if (ret)
 				return ret;
 		}
diff --git a/merge-recursive.c b/merge-recursive.c
index 1746c38..dcf1535 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2068,6 +2068,8 @@ int merge_recursive(struct merge_options *o,
 		commit_list_insert(h2, &(*result)->parents->next);
 	}
 	flush_output(o);
+	if (o->buffer_output < 2)
+		strbuf_release(&o->obuf);
 	if (show(o, 2))
 		diff_warn_rename_limit("merge.renamelimit",
 				       o->needed_rename_limit, 0);
diff --git a/sequencer.c b/sequencer.c
index 286a435..ec50519 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	strbuf_release(&o.obuf);
 	if (clean < 0)
 		return clean;
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v4 16/16] merge-recursive: flush output buffer even when erroring out
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (14 preceding siblings ...)
  2016-07-22 12:25       ` [PATCH v4 15/16] Ensure that the output buffer is released after calling merge_trees() Johannes Schindelin
@ 2016-07-22 12:26       ` Johannes Schindelin
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-22 12:26 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Ever since 66a155b (Enable output buffering in merge-recursive.,
2007-01-14), we had a problem: When the merge failed in a fatal way, all
regular output was swallowed because we called die() and did not get a
chance to drain the output buffers.

To fix this, several modifications were necessary:

- we needed to stop die()ing, to give callers a chance to do something
  when an error occurred (in this case, flush the output buffers),

- we needed to delay printing the error message so that the caller can
  print the buffered output before that, and

- we needed to make sure that the output buffers are flushed even when
  the return value indicates an error.

The first two changes were introduced through earlier commits in this
patch series, and this commit addresses the third one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index dcf1535..501cfb5 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2059,6 +2059,7 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	flush_output(o);
 	if (clean < 0)
 		return clean;
 
@@ -2067,7 +2068,6 @@ int merge_recursive(struct merge_options *o,
 		commit_list_insert(h1, &(*result)->parents);
 		commit_list_insert(h2, &(*result)->parents->next);
 	}
-	flush_output(o);
 	if (o->buffer_output < 2)
 		strbuf_release(&o->obuf);
 	if (show(o, 2))
-- 
2.9.0.281.g286a8d9

^ permalink raw reply related	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing
  2016-07-22 12:24       ` [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing Johannes Schindelin
@ 2016-07-25 21:39         ` Junio C Hamano
  2016-07-26 12:21           ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-25 21:39 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> +test_expect_success '--rebase with conflicts shows advice' '
> +	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
> +	git checkout -b seq &&
> +	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&

Make this more readble by using test-write-lines, perhaps?

> +	git add seq.txt &&
> +	test_tick &&
> +	git commit -m "Add seq.txt" &&
> +	printf "6\\n" >>seq.txt &&
> +	test_tick &&
> +	git commit -m "Append to seq.txt" seq.txt &&
> +	git checkout -b with-conflicts HEAD^ &&
> +	printf "conflicting\\n" >>seq.txt &&
> +	test_tick &&
> +	git commit -m "Create conflict" seq.txt &&
> +	test_must_fail git pull --rebase . seq 2>err >out &&
> +	grep "When you have resolved this problem" out
> +'
> +test_expect_success 'failed --rebase shows advice' '

Need a blank line before this one.

> +	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
> +	git checkout -b diverging &&
> +	test_commit attributes .gitattributes "* text=auto" attrs &&
> +	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
> +	git update-index --cacheinfo 0644 $sha1 file &&
> +	git commit -m v1-with-cr &&
> +	git checkout -f -b fails-to-rebase HEAD^ &&

It is unclear what the "-f" is for; is it attempting to clean up a
potential mess previous steps might have left?  We didn't have it in
the previous test above.

> +	test_commit v2-without-cr file "2" file2-lf &&
> +	test_must_fail git pull --rebase . diverging 2>err >out &&
> +	grep "When you have resolved this problem" out
> +'
> +
>  test_expect_success '--rebase fails with multiple branches' '
>  	git reset --hard before-rebase &&
>  	test_must_fail git pull --rebase . copy master 2>err &&

Not worth a reroll but after this series settles we would probably
want to address some of the above up with a follow-up clean-up patch.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 02/16] Report bugs consistently
  2016-07-22 12:24       ` [PATCH v4 02/16] Report bugs consistently Johannes Schindelin
@ 2016-07-25 21:44         ` Junio C Hamano
  2016-07-25 22:17           ` Jeff King
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-25 21:44 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> diff --git a/imap-send.c b/imap-send.c
> index db0fafe..67d67f8 100644
> --- a/imap-send.c
> +++ b/imap-send.c
> @@ -506,12 +506,12 @@ static char *next_arg(char **s)
>  
>  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
>  {
> -	int ret;
> +	int ret = -1;
>  	va_list va;
>  
>  	va_start(va, fmt);
>  	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
> -		die("Fatal: buffer too small. Please report a bug.");
> +		die("BUG: buffer too small (%d < %d)", ret, blen);
>  	va_end(va);
>  	return ret;
>  }

If "you gave me this size but you need at least this much" is truly
worth reporting, then this is misleading (ret is shown as -1 but you
do not even know how much is necessary).  In any case, this should
be done as a separate step anyway.

All the other hunks looked sensible.

Thanks.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 06/16] merge_recursive: abort properly upon errors
  2016-07-22 12:25       ` [PATCH v4 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-07-25 22:09         ` Junio C Hamano
  2016-07-26 12:26           ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-25 22:09 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> There are a couple of places where return values indicating errors
> are ignored. Let's teach them manners.

That is because the return value never indicated errors before this
series, isn't it?  A true error used to be expressed by dying, and
the return value indicating "cleanliness" of the merge were
deliberately ignored.

The world order changed by previous patches in this series and the
callers need to be updated to take the new kind of return values
into account.  That is not teaching them manners ;-)

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  merge-recursive.c | 10 ++++++++--
>  1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/merge-recursive.c b/merge-recursive.c
> index dc3182b..2d4cb80 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -1949,8 +1949,9 @@ int merge_recursive(struct merge_options *o,
>  		saved_b2 = o->branch2;
>  		o->branch1 = "Temporary merge branch 1";
>  		o->branch2 = "Temporary merge branch 2";
> -		merge_recursive(o, merged_common_ancestors, iter->item,
> -				NULL, &merged_common_ancestors);
> +		if (merge_recursive(o, merged_common_ancestors, iter->item,
> +				    NULL, &merged_common_ancestors) < 0)
> +			return -1;
>  		o->branch1 = saved_b1;
>  		o->branch2 = saved_b2;
>  		o->call_depth--;

This hunk feels somewhat wrong as-is.

There is a comment before the pre-context explaining why cleanness
flag is ignored.  It needs to be updated.  We still do not care
about cleanliness, i.e. 0=clean, 1=merged with conflict, but we now
can get negative values so we need to reject and return early if
this call indicates an error.

Thee other two hunks make sense.

Thanks.

> @@ -1966,6 +1967,8 @@ int merge_recursive(struct merge_options *o,
>  	o->ancestor = "merged common ancestors";
>  	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
>  			    &mrtree);
> +	if (clean < 0)
> +		return clean;
>  
>  	if (o->call_depth) {
>  		*result = make_virtual_commit(mrtree, "merged tree");
> @@ -2022,6 +2025,9 @@ int merge_recursive_generic(struct merge_options *o,
>  	hold_locked_index(lock, 1);
>  	clean = merge_recursive(o, head_commit, next_commit, ca,
>  			result);
> +	if (clean < 0)
> +		return clean;
> +
>  	if (active_cache_changed &&
>  	    write_locked_index(&the_index, lock, COMMIT_LOCK))
>  		return error(_("Unable to write index."));

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 02/16] Report bugs consistently
  2016-07-25 21:44         ` Junio C Hamano
@ 2016-07-25 22:17           ` Jeff King
  2016-07-25 22:36             ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Jeff King @ 2016-07-25 22:17 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, git, Eric Sunshine, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

On Mon, Jul 25, 2016 at 02:44:25PM -0700, Junio C Hamano wrote:

> > diff --git a/imap-send.c b/imap-send.c
> > index db0fafe..67d67f8 100644
> > --- a/imap-send.c
> > +++ b/imap-send.c
> > @@ -506,12 +506,12 @@ static char *next_arg(char **s)
> >  
> >  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
> >  {
> > -	int ret;
> > +	int ret = -1;
> >  	va_list va;
> >  
> >  	va_start(va, fmt);
> >  	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
> > -		die("Fatal: buffer too small. Please report a bug.");
> > +		die("BUG: buffer too small (%d < %d)", ret, blen);
> >  	va_end(va);
> >  	return ret;
> >  }
> 
> If "you gave me this size but you need at least this much" is truly
> worth reporting, then this is misleading (ret is shown as -1 but you
> do not even know how much is necessary).  In any case, this should
> be done as a separate step anyway.

Hrm, isn't "ret" going to be the necessary size? According to the
standard, it should tell us how many bytes were needed, not "-1" (this
is the "your vsnprintf is broken" case handled by the strbuf code).

I do think the numbers are reversed, though. It should be "blen < ret".

-Peff

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 11/16] am -3: use merge_recursive() directly again
  2016-07-22 12:25       ` [PATCH v4 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
@ 2016-07-25 22:19         ` Junio C Hamano
  2016-07-26 12:30           ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-25 22:19 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> Note: the code now calls merge_recursive_generic() again. Unlike
> merge_trees() and merge_recursive(), this function returns 0 upon success,
> as most of Git's functions. Therefore, the error value -1 naturally is
> handled correctly, and we do not have to take care of it specifically.

I've finished reading through up to this point and I'd stop for
now.

Some of the patches I didn't look beyond the context presented in
the patches, so it is very possible that I missed leaks caused by
early returns and things like that, but I didn't see anything
glaringly wrong.  Looks very promising.

Thanks.

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 02/16] Report bugs consistently
  2016-07-25 22:17           ` Jeff King
@ 2016-07-25 22:36             ` Junio C Hamano
  2016-07-26 12:24               ` Johannes Schindelin
  0 siblings, 1 reply; 262+ messages in thread
From: Junio C Hamano @ 2016-07-25 22:36 UTC (permalink / raw)
  To: Jeff King
  Cc: Johannes Schindelin, git, Eric Sunshine, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Jeff King <peff@peff.net> writes:

> On Mon, Jul 25, 2016 at 02:44:25PM -0700, Junio C Hamano wrote:
>
>> > diff --git a/imap-send.c b/imap-send.c
>> > index db0fafe..67d67f8 100644
>> > --- a/imap-send.c
>> > +++ b/imap-send.c
>> > @@ -506,12 +506,12 @@ static char *next_arg(char **s)
>> >  
>> >  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
>> >  {
>> > -	int ret;
>> > +	int ret = -1;
>> >  	va_list va;
>> >  
>> >  	va_start(va, fmt);
>> >  	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
>> > -		die("Fatal: buffer too small. Please report a bug.");
>> > +		die("BUG: buffer too small (%d < %d)", ret, blen);
>> >  	va_end(va);
>> >  	return ret;
>> >  }
>> 
>> If "you gave me this size but you need at least this much" is truly
>> worth reporting, then this is misleading (ret is shown as -1 but you
>> do not even know how much is necessary).  In any case, this should
>> be done as a separate step anyway.
>
> Hrm, isn't "ret" going to be the necessary size? According to the
> standard, it should tell us how many bytes were needed, not "-1" (this
> is the "your vsnprintf is broken" case handled by the strbuf code).

Yes.  If blen <= 0, we do not even do vsnprintf() and that is why
Dscho added "int ret = -1" initialization; otherwise his new die()
would end up referencing uninitialized ret.

> I do think the numbers are reversed, though. It should be "blen < ret".

That, too ;-)

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 01/16] Verify that `git pull --rebase` shows the helpful advice when failing
  2016-07-25 21:39         ` Junio C Hamano
@ 2016-07-26 12:21           ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 12:21 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Hi Junio,

On Mon, 25 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > +test_expect_success '--rebase with conflicts shows advice' '
> > +	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
> > +	git checkout -b seq &&
> > +	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&
> 
> Make this more readble by using test-write-lines, perhaps?

Or even test_seq. Thanks for pointing my nose to this.

> > +	git add seq.txt &&
> > +	test_tick &&
> > +	git commit -m "Add seq.txt" &&
> > +	printf "6\\n" >>seq.txt &&
> > +	test_tick &&
> > +	git commit -m "Append to seq.txt" seq.txt &&
> > +	git checkout -b with-conflicts HEAD^ &&
> > +	printf "conflicting\\n" >>seq.txt &&
> > +	test_tick &&
> > +	git commit -m "Create conflict" seq.txt &&
> > +	test_must_fail git pull --rebase . seq 2>err >out &&
> > +	grep "When you have resolved this problem" out
> > +'
> > +test_expect_success 'failed --rebase shows advice' '
> 
> Need a blank line before this one.

Yep, sorry.

> > +	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
> > +	git checkout -b diverging &&
> > +	test_commit attributes .gitattributes "* text=auto" attrs &&
> > +	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
> > +	git update-index --cacheinfo 0644 $sha1 file &&
> > +	git commit -m v1-with-cr &&
> > +	git checkout -f -b fails-to-rebase HEAD^ &&
> 
> It is unclear what the "-f" is for; is it attempting to clean up a
> potential mess previous steps might have left?  We didn't have it in
> the previous test above.

It is there to clean up a very non-potential mess: forcing a CR/LF into a
file marked with `text=auto` makes a royal mess. Neither `git reset
--hard` nor `git stash` will make that file clean. As a consequence, the
`git checkout` without an `-f` would *always* fail "because of uncommitted
changes".

I clarified that in a comment.

> > +	test_commit v2-without-cr file "2" file2-lf &&
> > +	test_must_fail git pull --rebase . diverging 2>err >out &&
> > +	grep "When you have resolved this problem" out
> > +'
> > +
> >  test_expect_success '--rebase fails with multiple branches' '
> >  	git reset --hard before-rebase &&
> >  	test_must_fail git pull --rebase . copy master 2>err &&
> 
> Not worth a reroll but after this series settles we would probably
> want to address some of the above up with a follow-up clean-up patch.

As I re-roll anyway, no big deal.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 02/16] Report bugs consistently
  2016-07-25 22:36             ` Junio C Hamano
@ 2016-07-26 12:24               ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 12:24 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, git, Eric Sunshine, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Hi Junio & Peff,

On Mon, 25 Jul 2016, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > On Mon, Jul 25, 2016 at 02:44:25PM -0700, Junio C Hamano wrote:
> >
> >> > diff --git a/imap-send.c b/imap-send.c
> >> > index db0fafe..67d67f8 100644
> >> > --- a/imap-send.c
> >> > +++ b/imap-send.c
> >> > @@ -506,12 +506,12 @@ static char *next_arg(char **s)
> >> >  
> >> >  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
> >> >  {
> >> > -	int ret;
> >> > +	int ret = -1;
> >> >  	va_list va;
> >> >  
> >> >  	va_start(va, fmt);
> >> >  	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
> >> > -		die("Fatal: buffer too small. Please report a bug.");
> >> > +		die("BUG: buffer too small (%d < %d)", ret, blen);
> >> >  	va_end(va);
> >> >  	return ret;
> >> >  }
> >> 
> >> If "you gave me this size but you need at least this much" is truly
> >> worth reporting, then this is misleading (ret is shown as -1 but you
> >> do not even know how much is necessary).  In any case, this should
> >> be done as a separate step anyway.
> >
> > Hrm, isn't "ret" going to be the necessary size? According to the
> > standard, it should tell us how many bytes were needed, not "-1" (this
> > is the "your vsnprintf is broken" case handled by the strbuf code).
> 
> Yes.  If blen <= 0, we do not even do vsnprintf() and that is why
> Dscho added "int ret = -1" initialization; otherwise his new die()
> would end up referencing uninitialized ret.

Exactly. While I was fixing this bug message, it occurred to me that it
makes little sense to ask a user to report a bug when it is unknown how
small the buffer was and what would have been the desired buffer size. So
I did a fly-by fix.

However, it is true that this is completely outside the purpose of this
patch series (in fact, most of this patch is completely outside the
purpose, and I am regretting that dearly, as the patch series' submission
already takes almost a month, and I only now get people to review the
critical parts of the changes).

So I simply backed out the more verbose message and *only* do the obvious
thing: replace the "Fatal:" prefix by a "BUG:" one.

> > I do think the numbers are reversed, though. It should be "blen < ret".
> 
> That, too ;-)

True. All the better that I reverted that part of the patch ;-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 06/16] merge_recursive: abort properly upon errors
  2016-07-25 22:09         ` Junio C Hamano
@ 2016-07-26 12:26           ` Johannes Schindelin
  0 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 12:26 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Hi Junio,

On Mon, 25 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > There are a couple of places where return values indicating errors
> > are ignored. Let's teach them manners.
> 
> That is because the return value never indicated errors before this
> series, isn't it?  A true error used to be expressed by dying, and
> the return value indicating "cleanliness" of the merge were
> deliberately ignored.
> 
> The world order changed by previous patches in this series and the
> callers need to be updated to take the new kind of return values
> into account.  That is not teaching them manners ;-)

I reworded that commit message.

> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  merge-recursive.c | 10 ++++++++--
> >  1 file changed, 8 insertions(+), 2 deletions(-)
> >
> > diff --git a/merge-recursive.c b/merge-recursive.c
> > index dc3182b..2d4cb80 100644
> > --- a/merge-recursive.c
> > +++ b/merge-recursive.c
> > @@ -1949,8 +1949,9 @@ int merge_recursive(struct merge_options *o,
> >  		saved_b2 = o->branch2;
> >  		o->branch1 = "Temporary merge branch 1";
> >  		o->branch2 = "Temporary merge branch 2";
> > -		merge_recursive(o, merged_common_ancestors, iter->item,
> > -				NULL, &merged_common_ancestors);
> > +		if (merge_recursive(o, merged_common_ancestors, iter->item,
> > +				    NULL, &merged_common_ancestors) < 0)
> > +			return -1;
> >  		o->branch1 = saved_b1;
> >  		o->branch2 = saved_b2;
> >  		o->call_depth--;
> 
> This hunk feels somewhat wrong as-is.
> 
> There is a comment before the pre-context explaining why cleanness
> flag is ignored.  It needs to be updated.  We still do not care
> about cleanliness, i.e. 0=clean, 1=merged with conflict, but we now
> can get negative values so we need to reject and return early if
> this call indicates an error.

I updated that comment. Hopefully now the hunk makes sense ;-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* Re: [PATCH v4 11/16] am -3: use merge_recursive() directly again
  2016-07-25 22:19         ` Junio C Hamano
@ 2016-07-26 12:30           ` Johannes Schindelin
  2016-07-26 17:12             ` Junio C Hamano
  0 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 12:30 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Jeff King, Johannes Sixt, Duy Nguyen,
	Jakub Narębski

Hi Junio,

On Mon, 25 Jul 2016, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > Note: the code now calls merge_recursive_generic() again. Unlike
> > merge_trees() and merge_recursive(), this function returns 0 upon success,
> > as most of Git's functions. Therefore, the error value -1 naturally is
> > handled correctly, and we do not have to take care of it specifically.
> 
> I've finished reading through up to this point and I'd stop for
> now.

If you want, I can break out the subsequent patches into a separate
series. I just thought that you might want to have them here, as I
implemented them in response to the concern you raised in a previous
iteration of the same patch series: you pointed out that returning a
negative error value still does not let the caller handle the error
message, and with the subsequent patches that is now possible, too.

> Some of the patches I didn't look beyond the context presented in
> the patches, so it is very possible that I missed leaks caused by
> early returns and things like that, but I didn't see anything
> glaringly wrong.  Looks very promising.

Thanks.

I did try my best to catch all resource leaks, but I did stare at those
patches so often and so long that it is easy for some obvious bug to have
slipped by. Therefore, I would appreciate it if you (or somebody as
diligent as you) could have a careful look in particular at the
"merge-recursive: switch to returning errors instead of dying" patch.

I may have missed something as stupid as an unclosed file handle, after
all.

Thank you,
Dscho

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v5 00/16] Use merge_recursive() directly in the builtin am
  2016-07-22 12:23     ` [PATCH v4 " Johannes Schindelin
                         ` (15 preceding siblings ...)
  2016-07-22 12:26       ` [PATCH v4 16/16] merge-recursive: flush output buffer even when erroring out Johannes Schindelin
@ 2016-07-26 16:05       ` Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 01/16] t5520: verify that `pull --rebase` shows the helpful advice when failing Johannes Schindelin
                           ` (16 more replies)
  16 siblings, 17 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:05 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

This is the fifth iteration of the long-awaited re-roll of the attempt to
avoid spawning merge-recursive from the builtin am and use merge_recursive()
directly instead.

The *real* reason for the reroll is that I need a libified recursive
merge to accelerate the interactive rebase by teaching the sequencer to
do rebase -i's grunt work. Coming with a very nice 3x-5x speedup of
`rebase -i`.

In this endeavor, we need to be extra careful to retain backwards
compatibility. The test script t6022-merge-rename.sh, for example, verifies
that `git pull` exits with status 128 in case of a fatal error. To that end,
we need to make sure that fatal errors are handled by existing (builtin)
users via exit(128) (or die(), which calls exit(128) at the end).  New users
(such as a builtin helper doing rebase -i's grunt work) may want to print
some helpful advice what happened and how to get out of this mess before
erroring out.

The changes relative to the fourth iteration of this patch series:

- the first patch which introduces a tests case to t5520 was prettified:

  - it uses test_seq now,
  - it avoids `printf` when `echo` does the job, too,
  - it adds a missing empty line between test cases, and
  - it clarifies why we need to check out with force.

- the change that would have made the bug report about a too-small
  buffer in imap-send was reverted, because it did more than was claimed
  by the commit message.

- the "Let's teach them manners" part of one commit message was replaced
  with a less flippant description.

- the comment before merge_recursive() that claims that the return value
  is ignored now clarifies that it is ignored unless it indicates an
  error.

- during the rebase to `master`, a trivial merge conflict with the
  `jc/renormalize-merge-kill-safer-crlf` branch was resolved.

This patch series touches rather important code. Now that I addressed
concerns such as prettifying some test code, I would appreciate thorough
reviews with a focus on the critical parts of the code, those that could
result in regressions.


Johannes Schindelin (16):
  t5520: verify that `pull --rebase` shows the helpful advice when
    failing
  Report bugs consistently
  Avoid translating bug messages
  merge-recursive: clarify code in was_tracked()
  Prepare the builtins for a libified merge_recursive()
  merge_recursive: abort properly upon errors
  merge-recursive: avoid returning a wholesale struct
  merge-recursive: allow write_tree_from_memory() to error out
  merge-recursive: handle return values indicating errors
  merge-recursive: switch to returning errors instead of dying
  am -3: use merge_recursive() directly again
  merge-recursive: flush output buffer before printing error messages
  merge-recursive: write the commit title in one go
  merge-recursive: offer an option to retain the output in 'obuf'
  Ensure that the output buffer is released after calling merge_trees()
  merge-recursive: flush output buffer even when erroring out

 builtin/am.c           |  62 ++----
 builtin/checkout.c     |   5 +-
 builtin/ls-files.c     |   3 +-
 builtin/merge.c        |   2 +
 builtin/update-index.c |   2 +-
 grep.c                 |   8 +-
 imap-send.c            |   2 +-
 merge-recursive.c      | 578 +++++++++++++++++++++++++++++--------------------
 merge-recursive.h      |   2 +-
 sequencer.c            |   5 +
 sha1_file.c            |   4 +-
 t/t5520-pull.sh        |  32 +++
 trailer.c              |   2 +-
 transport.c            |   2 +-
 wt-status.c            |   4 +-
 15 files changed, 418 insertions(+), 295 deletions(-)

Published-As: https://github.com/dscho/git/releases/tag/am-3-merge-recursive-direct-v5
Interdiff vs v4:

 diff --git a/imap-send.c b/imap-send.c
 index 67d67f8..0f5f476 100644
 --- a/imap-send.c
 +++ b/imap-send.c
 @@ -506,12 +506,12 @@ static char *next_arg(char **s)
  
  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
  {
 -	int ret = -1;
 +	int ret;
  	va_list va;
  
  	va_start(va, fmt);
  	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
 -		die("BUG: buffer too small (%d < %d)", ret, blen);
 +		die("BUG: buffer too small. Please report a bug.");
  	va_end(va);
  	return ret;
  }
 diff --git a/merge-recursive.c b/merge-recursive.c
 index a3d12e6..66e93e0 100644
 --- a/merge-recursive.c
 +++ b/merge-recursive.c
 @@ -2041,9 +2041,10 @@ int merge_recursive(struct merge_options *o,
  		/*
  		 * When the merge fails, the result contains files
  		 * with conflict markers. The cleanness flag is
 -		 * ignored, it was never actually used, as result of
 -		 * merge_trees has always overwritten it: the committed
 -		 * "conflicts" were already resolved.
 +		 * ignored (unless indicating an error), it was never
 +		 * actually used, as result of merge_trees has always
 +		 * overwritten it: the committed "conflicts" were
 +		 * already resolved.
  		 */
  		discard_cache();
  		saved_b1 = o->branch1;
 diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
 index d289056..6ad37b5 100755
 --- a/t/t5520-pull.sh
 +++ b/t/t5520-pull.sh
 @@ -258,20 +258,21 @@ test_expect_success '--rebase' '
  test_expect_success '--rebase with conflicts shows advice' '
  	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
  	git checkout -b seq &&
 -	printf "1\\n2\\n3\\n4\\n5\\n" >seq.txt &&
 +	test_seq 5 >seq.txt &&
  	git add seq.txt &&
  	test_tick &&
  	git commit -m "Add seq.txt" &&
 -	printf "6\\n" >>seq.txt &&
 +	echo 6 >>seq.txt &&
  	test_tick &&
  	git commit -m "Append to seq.txt" seq.txt &&
  	git checkout -b with-conflicts HEAD^ &&
 -	printf "conflicting\\n" >>seq.txt &&
 +	echo conflicting >>seq.txt &&
  	test_tick &&
  	git commit -m "Create conflict" seq.txt &&
  	test_must_fail git pull --rebase . seq 2>err >out &&
  	grep "When you have resolved this problem" out
  '
 +
  test_expect_success 'failed --rebase shows advice' '
  	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
  	git checkout -b diverging &&
 @@ -279,6 +280,7 @@ test_expect_success 'failed --rebase shows advice' '
  	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
  	git update-index --cacheinfo 0644 $sha1 file &&
  	git commit -m v1-with-cr &&
 +	# force checkout because `git reset --hard` will not leave clean `file`
  	git checkout -f -b fails-to-rebase HEAD^ &&
  	test_commit v2-without-cr file "2" file2-lf &&
  	test_must_fail git pull --rebase . diverging 2>err >out &&

-- 
2.9.0.281.g286a8d9

base-commit: 8c6d1f9807c67532e7fb545a944b064faff0f70b

^ permalink raw reply	[flat|nested] 262+ messages in thread

* [PATCH v5 01/16] t5520: verify that `pull --rebase` shows the helpful advice when failing
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
@ 2016-07-26 16:05         ` Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 02/16] Report bugs consistently Johannes Schindelin
                           ` (15 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:05 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It was noticed by Brendan Forster last October that the builtin `git am`
regressed on that. Our hot fix reverted to spawning the recursive merge
instead of using it as a library function.

As we are about to revert that hot fix, after making the recursive merge a
true library function (i.e. a function that does not die() in case of
"normal" errors), let's add a test that verifies that we do not regress on
the same problem which made the hot fix necessary in the first place.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5520-pull.sh | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 37ebbcf..6ad37b5 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -255,6 +255,38 @@ test_expect_success '--rebase' '
 	test new = "$(git show HEAD:file2)"
 '
 
+test_expect_success '--rebase with conflicts shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b seq &&
+	test_seq 5 >seq.txt &&
+	git add seq.txt &&
+	test_tick &&
+	git commit -m "Add seq.txt" &&
+	echo 6 >>seq.txt &&
+	test_tick &&
+	git commit -m "Append to seq.txt" seq.txt &&
+	git checkout -b with-conflicts HEAD^ &&
+	echo conflicting >>seq.txt &&
+	test_tick &&
+	git commit -m "Create conflict" seq.txt &&
+	test_must_fail git pull --rebase . seq 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+
+test_expect_success 'failed --rebase shows advice' '
+	test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
+	git checkout -b diverging &&
+	test_commit attributes .gitattributes "* text=auto" attrs &&
+	sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" &&
+	git update-index --cacheinfo 0644 $sha1 file &&
+	git commit -m v1-with-cr &&
+	# force checkout because `git reset --hard` will not leave clean `file`
+	git checkout -f -b fails-to-rebase HEAD^ &&
+	test_commit v2-without-cr file "2" file2-lf &&
+	test_must_fail git pull --rebase . diverging 2>err >out &&
+	grep "When you have resolved this problem" out
+'
+
 test_expect_success '--rebase fails with multiple branches' '
 	git reset --hard before-rebase &&
 	test_must_fail git pull --rebase . copy master 2>err &&
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 02/16] Report bugs consistently
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 01/16] t5520: verify that `pull --rebase` shows the helpful advice when failing Johannes Schindelin
@ 2016-07-26 16:05         ` Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 03/16] Avoid translating bug messages Johannes Schindelin
                           ` (14 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:05 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The vast majority of error messages in Git's source code which report a
bug use the convention to prefix the message with "BUG:".

As part of cleaning up merge-recursive to stop die()ing except in case of
detected bugs, let's just make the remainder of the bug reports consistent
with the de facto rule.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/ls-files.c     |  3 ++-
 builtin/update-index.c |  2 +-
 grep.c                 |  8 ++++----
 imap-send.c            |  2 +-
 merge-recursive.c      | 15 +++++++--------
 sha1_file.c            |  4 ++--
 trailer.c              |  2 +-
 transport.c            |  2 +-
 wt-status.c            |  4 ++--
 9 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f02e3d2..00ea91a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -118,7 +118,8 @@ static void show_killed_files(struct dir_struct *dir)
 				 */
 				pos = cache_name_pos(ent->name, ent->len);
 				if (0 <= pos)
-					die("bug in show-killed-files");
+					die("BUG: killed-file %.*s not found",
+						ent->len, ent->name);
 				pos = -pos - 1;
 				while (pos < active_nr &&
 				       ce_stage(active_cache[pos]))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 6cdfd5f..ba04b19 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -1146,7 +1146,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
 		break;
 	default:
-		die("Bug: bad untracked_cache value: %d", untracked_cache);
+		die("BUG: bad untracked_cache value: %d", untracked_cache);
 	}
 
 	if (active_cache_changed) {
diff --git a/grep.c b/grep.c
index 394c856..22cbb73 100644
--- a/grep.c
+++ b/grep.c
@@ -693,10 +693,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 	for (p = opt->header_list; p; p = p->next) {
 		if (p->token != GREP_PATTERN_HEAD)
-			die("bug: a non-header pattern in grep header list.");
+			die("BUG: a non-header pattern in grep header list.");
 		if (p->field < GREP_HEADER_FIELD_MIN ||
 		    GREP_HEADER_FIELD_MAX <= p->field)
-			die("bug: unknown header field %d", p->field);
+			die("BUG: unknown header field %d", p->field);
 		compile_regexp(p, opt);
 	}
 
@@ -709,7 +709,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
 		h = compile_pattern_atom(&pp);
 		if (!h || pp != p->next)
-			die("bug: malformed header expr");
+			die("BUG: malformed header expr");
 		if (!header_group[p->field]) {
 			header_group[p->field] = h;
 			continue;
@@ -1514,7 +1514,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 		case GREP_BINARY_TEXT:
 			break;
 		default:
-			die("bug: unknown binary handling mode");
+			die("BUG: unknown binary handling mode");
 		}
 	}
 
diff --git a/imap-send.c b/imap-send.c
index db0fafe..0f5f476 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -511,7 +511,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 
 	va_start(va, fmt);
 	if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-		die("Fatal: buffer too small. Please report a bug.");
+		die("BUG: buffer too small. Please report a bug.");
 	va_end(va);
 	return ret;
 }
diff --git a/merge-recursive.c b/merge-recursive.c
index a4a1195..4338b73 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -268,7 +268,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 				fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
 					(int)ce_namelen(ce), ce->name);
 		}
-		die("Bug in merge-recursive.c");
+		die("BUG: unmerged index entries in merge-recursive.c");
 	}
 
 	if (!active_cache_tree)
@@ -966,9 +966,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 
 			if (!oid_eq(&a->oid, &b->oid))
 				result.clean = 0;
-		} else {
-			die(_("unsupported object type in the tree"));
-		}
+		} else
+			die(_("BUG: unsupported object type in the tree"));
 	}
 
 	return result;
@@ -1354,7 +1353,7 @@ static int process_renames(struct merge_options *o,
 			const char *ren2_dst = ren2->pair->two->path;
 			enum rename_type rename_type;
 			if (strcmp(ren1_src, ren2_src) != 0)
-				die("ren1_src != ren2_src");
+				die("BUG: ren1_src != ren2_src");
 			ren2->dst_entry->processed = 1;
 			ren2->processed = 1;
 			if (strcmp(ren1_dst, ren2_dst) != 0) {
@@ -1388,7 +1387,7 @@ static int process_renames(struct merge_options *o,
 			ren2 = lookup->util;
 			ren2_dst = ren2->pair->two->path;
 			if (strcmp(ren1_dst, ren2_dst) != 0)
-				die("ren1_dst != ren2_dst");
+				die("BUG: ren1_dst != ren2_dst");
 
 			clean_merge = 0;
 			ren2->processed = 1;
@@ -1812,7 +1811,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("Fatal merge failure, shouldn't happen."));
+		die(_("BUG: fatal merge failure, shouldn't happen."));
 
 	return clean_merge;
 }
@@ -1870,7 +1869,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("Unprocessed path??? %s"),
+				die(_("BUG: unprocessed path??? %s"),
 				    entries->items[i].string);
 		}
 
diff --git a/sha1_file.c b/sha1_file.c
index d5e1121..5085fe0 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -795,7 +795,7 @@ void close_all_packs(void)
 
 	for (p = packed_git; p; p = p->next)
 		if (p->do_not_close)
-			die("BUG! Want to close pack marked 'do-not-close'");
+			die("BUG: want to close pack marked 'do-not-close'");
 		else
 			close_pack(p);
 }
@@ -2330,7 +2330,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
 		if (data)
-			die("BUG in unpack_entry: left loop at a valid delta");
+			die("BUG: unpack_entry: left loop at a valid delta");
 		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
diff --git a/trailer.c b/trailer.c
index 8e48a5c..c6ea9ac 100644
--- a/trailer.c
+++ b/trailer.c
@@ -562,7 +562,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 		break;
 	default:
-		die("internal bug in trailer.c");
+		die("BUG: trailer.c: unhandled type %d", type);
 	}
 	return 0;
 }
diff --git a/transport.c b/transport.c
index b233e3e..04d9454 100644
--- a/transport.c
+++ b/transport.c
@@ -566,7 +566,7 @@ void transport_take_over(struct transport *transport,
 	struct git_transport_data *data;
 
 	if (!transport->smart_options)
-		die("Bug detected: Taking over transport requires non-NULL "
+		die("BUG: taking over transport requires non-NULL "
 		    "smart_options field.");
 
 	data = xcalloc(1, sizeof(*data));
diff --git a/wt-status.c b/wt-status.c
index 19cbc39..f8ae0c2 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die("bug: unhandled unmerged status %x", stagemask);
+		die("BUG: unhandled unmerged status %x", stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die("bug: unhandled diff status %c", status);
+		die("BUG: unhandled diff status %c", status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 03/16] Avoid translating bug messages
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 01/16] t5520: verify that `pull --rebase` shows the helpful advice when failing Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 02/16] Report bugs consistently Johannes Schindelin
@ 2016-07-26 16:05         ` Johannes Schindelin
  2016-07-26 16:05         ` [PATCH v5 04/16] merge-recursive: clarify code in was_tracked() Johannes Schindelin
                           ` (13 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:05 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

While working on the patch series that avoids die()ing in recursive
merges, the issue came up that bug reports (i.e. die("BUG: ...")
constructs) should never be translated, as the target audience is the
Git developer community, not necessarily the current user, and hence
a translated message would make it *harder* to address the problem.

So let's stop translating the obvious ones. As it is really, really
outside the purview of this patch series to see whether there are more
die() statements that report bugs and are currently translated, that
task is left for another day and patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 4338b73..1b6db87 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -967,7 +967,7 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 			if (!oid_eq(&a->oid, &b->oid))
 				result.clean = 0;
 		} else
-			die(_("BUG: unsupported object type in the tree"));
+			die("BUG: unsupported object type in the tree");
 	}
 
 	return result;
@@ -1811,7 +1811,7 @@ static int process_entry(struct merge_options *o,
 		 */
 		remove_file(o, 1, path, !a_mode);
 	} else
-		die(_("BUG: fatal merge failure, shouldn't happen."));
+		die("BUG: fatal merge failure, shouldn't happen.");
 
 	return clean_merge;
 }
@@ -1869,7 +1869,7 @@ int merge_trees(struct merge_options *o,
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
 			if (!e->processed)
-				die(_("BUG: unprocessed path??? %s"),
+				die("BUG: unprocessed path??? %s",
 				    entries->items[i].string);
 		}
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 04/16] merge-recursive: clarify code in was_tracked()
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (2 preceding siblings ...)
  2016-07-26 16:05         ` [PATCH v5 03/16] Avoid translating bug messages Johannes Schindelin
@ 2016-07-26 16:05         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 05/16] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
                           ` (12 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:05 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It can be puzzling to see that was_tracked() asks to get an index entry
by name, but does not take a negative return value for an answer.

The reason we have to do this is that cache_name_pos() only looks for
entries in stage 0, even if nobody asked for any stage in particular.

Let's rewrite the logic a little bit, to handle the easy case early: if
cache_name_pos() returned a non-negative position, we know it is a match,
and we do not even have to compare the name again (cache_name_pos() did
that for us already). We can say right away: yes, this file was tracked.

Only if there was no exact match do we need to look harder for any
matching entry in stage 2.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 30 ++++++++++++++----------------
 1 file changed, 14 insertions(+), 16 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 1b6db87..3a652b7 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -667,23 +667,21 @@ static int was_tracked(const char *path)
 {
 	int pos = cache_name_pos(path, strlen(path));
 
-	if (pos < 0)
-		pos = -1 - pos;
-	while (pos < active_nr &&
-	       !strcmp(path, active_cache[pos]->name)) {
-		/*
-		 * If stage #0, it is definitely tracked.
-		 * If it has stage #2 then it was tracked
-		 * before this merge started.  All other
-		 * cases the path was not tracked.
-		 */
-		switch (ce_stage(active_cache[pos])) {
-		case 0:
-		case 2:
+	if (0 <= pos)
+		/* we have been tracking this path */
+		return 1;
+
+	/*
+	 * Look for an unmerged entry for the path,
+	 * specifically stage #2, which would indicate
+	 * that "our" side before the merge started
+	 * had the path tracked (and resulted in a conflict).
+	 */
+	for (pos = -1 - pos;
+	     pos < active_nr && !strcmp(path, active_cache[pos]->name);
+	     pos++)
+		if (ce_stage(active_cache[pos]) == 2)
 			return 1;
-		}
-		pos++;
-	}
 	return 0;
 }
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 05/16] Prepare the builtins for a libified merge_recursive()
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (3 preceding siblings ...)
  2016-07-26 16:05         ` [PATCH v5 04/16] merge-recursive: clarify code in was_tracked() Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
                           ` (11 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Previously, callers of merge_trees() or merge_recursive() expected that
code to die() with an error message. This used to be okay because we
called those commands from scripts, and had a chance to print out a
message in case the command failed fatally (read: with exit code 128).

As scripting incurs its own set of problems (portability, speed,
idiosynchracies of different shells, limited data structures leading to
inefficient code), we are converting more and more of these scripts into
builtins, using library functions directly.

We already tried to use merge_recursive() directly in the builtin
git-am, for example. Unfortunately, we had to roll it back temporarily
because some of the code in merge-recursive.c still deemed it okay to
call die(), when the builtin am code really wanted to print out a useful
advice after the merge failed fatally. In the next commits, we want to
fix that.

The code touched by this commit expected merge_trees() to die() with
some useful message when there is an error condition, but merge_trees()
is going to be improved by converting all die() calls to return error()
instead (i.e. return value -1 after printing out the message as before),
so that the caller can react more flexibly.

This is a step to prepare for the version of merge_trees() that no
longer dies,  even if we just imitate the previous behavior by calling
exit(128): this is what callers of e.g. `git merge` have come to expect.

Note that the callers of the sequencer (revert and cherry-pick) already
fail fast even for the return value -1; The only difference is that they
now get a chance to say "<command> failed".

A caller of merge_trees() might want handle error messages themselves
(or even suppress them). As this patch is already complex enough, we
leave that change for a later patch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/checkout.c | 4 +++-
 builtin/merge.c    | 2 ++
 sequencer.c        | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 27c1a05..07dea3b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -567,8 +567,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.ancestor = old->name;
 			o.branch1 = new->name;
 			o.branch2 = "local";
-			merge_trees(&o, new->commit->tree, work,
+			ret = merge_trees(&o, new->commit->tree, work,
 				old->commit->tree, &result);
+			if (ret < 0)
+				exit(128);
 			ret = reset_tree(new->commit->tree, opts, 0,
 					 writeout_error);
 			if (ret)
diff --git a/builtin/merge.c b/builtin/merge.c
index 19b3bc2..148a9a5 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -673,6 +673,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 		hold_locked_index(&lock, 1);
 		clean = merge_recursive(&o, head,
 				remoteheads->item, reversed, &result);
+		if (clean < 0)
+			exit(128);
 		if (active_cache_changed &&
 		    write_locked_index(&the_index, &lock, COMMIT_LOCK))
 			die (_("unable to write %s"), get_index_file());
diff --git a/sequencer.c b/sequencer.c
index cdfac82..286a435 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -293,6 +293,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	clean = merge_trees(&o,
 			    head_tree,
 			    next_tree, base_tree, &result);
+	if (clean < 0)
+		return clean;
 
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
@@ -559,6 +561,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
 					 head, &msgbuf, opts);
+		if (res < 0)
+			return res;
 		write_message(&msgbuf, git_path_merge_msg());
 	} else {
 		struct commit_list *common = NULL;
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 06/16] merge_recursive: abort properly upon errors
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (4 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 05/16] Prepare the builtins for a libified merge_recursive() Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 07/16] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
                           ` (10 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

There are a couple of places where return values never indicated errors
before, as wie simply died instead of returning.

But now negative return values mean that there was an error and we have to
abort the operation. Let's do exactly that.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 3a652b7..58ced25 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1949,17 +1949,19 @@ int merge_recursive(struct merge_options *o,
 		/*
 		 * When the merge fails, the result contains files
 		 * with conflict markers. The cleanness flag is
-		 * ignored, it was never actually used, as result of
-		 * merge_trees has always overwritten it: the committed
-		 * "conflicts" were already resolved.
+		 * ignored (unless indicating an error), it was never
+		 * actually used, as result of merge_trees has always
+		 * overwritten it: the committed "conflicts" were
+		 * already resolved.
 		 */
 		discard_cache();
 		saved_b1 = o->branch1;
 		saved_b2 = o->branch2;
 		o->branch1 = "Temporary merge branch 1";
 		o->branch2 = "Temporary merge branch 2";
-		merge_recursive(o, merged_common_ancestors, iter->item,
-				NULL, &merged_common_ancestors);
+		if (merge_recursive(o, merged_common_ancestors, iter->item,
+				    NULL, &merged_common_ancestors) < 0)
+			return -1;
 		o->branch1 = saved_b1;
 		o->branch2 = saved_b2;
 		o->call_depth--;
@@ -1975,6 +1977,8 @@ int merge_recursive(struct merge_options *o,
 	o->ancestor = "merged common ancestors";
 	clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
 			    &mrtree);
+	if (clean < 0)
+		return clean;
 
 	if (o->call_depth) {
 		*result = make_virtual_commit(mrtree, "merged tree");
@@ -2031,6 +2035,9 @@ int merge_recursive_generic(struct merge_options *o,
 	hold_locked_index(lock, 1);
 	clean = merge_recursive(o, head_commit, next_commit, ca,
 			result);
+	if (clean < 0)
+		return clean;
+
 	if (active_cache_changed &&
 	    write_locked_index(&the_index, lock, COMMIT_LOCK))
 		return error(_("Unable to write index."));
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 07/16] merge-recursive: avoid returning a wholesale struct
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (5 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 06/16] merge_recursive: abort properly upon errors Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 08/16] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
                           ` (9 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It is technically allowed, as per C89, for functions' return type to
be complete structs (i.e. *not* just pointers to structs).

However, it was just an oversight of this developer when converting
Python code to C code in 6d297f8 (Status update on merge-recursive in
C, 2006-07-08) which introduced such a return type.

Besides, by converting this construct to pass in the struct, we can now
start returning a value that can indicate errors in future patches. This
will help the current effort to libify merge-recursive.c.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 106 ++++++++++++++++++++++++++++--------------------------
 1 file changed, 56 insertions(+), 50 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 58ced25..2be1e17 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -894,47 +894,47 @@ static int merge_3way(struct merge_options *o,
 	return merge_status;
 }
 
-static struct merge_file_info merge_file_1(struct merge_options *o,
+static int merge_file_1(struct merge_options *o,
 					   const struct diff_filespec *one,
 					   const struct diff_filespec *a,
 					   const struct diff_filespec *b,
 					   const char *branch1,
-					   const char *branch2)
+					   const char *branch2,
+					   struct merge_file_info *result)
 {
-	struct merge_file_info result;
-	result.merge = 0;
-	result.clean = 1;
+	result->merge = 0;
+	result->clean = 1;
 
 	if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-		result.clean = 0;
+		result->clean = 0;
 		if (S_ISREG(a->mode)) {
-			result.mode = a->mode;
-			oidcpy(&result.oid, &a->oid);
+			result->mode = a->mode;
+			oidcpy(&result->oid, &a->oid);
 		} else {
-			result.mode = b->mode;
-			oidcpy(&result.oid, &b->oid);
+			result->mode = b->mode;
+			oidcpy(&result->oid, &b->oid);
 		}
 	} else {
 		if (!oid_eq(&a->oid, &one->oid) && !oid_eq(&b->oid, &one->oid))
-			result.merge = 1;
+			result->merge = 1;
 
 		/*
 		 * Merge modes
 		 */
 		if (a->mode == b->mode || a->mode == one->mode)
-			result.mode = b->mode;
+			result->mode = b->mode;
 		else {
-			result.mode = a->mode;
+			result->mode = a->mode;
 			if (b->mode != one->mode) {
-				result.clean = 0;
-				result.merge = 1;
+				result->clean = 0;
+				result->merge = 1;
 			}
 		}
 
 		if (oid_eq(&a->oid, &b->oid) || oid_eq(&a->oid, &one->oid))
-			oidcpy(&result.oid, &b->oid);
+			oidcpy(&result->oid, &b->oid);
 		else if (oid_eq(&b->oid, &one->oid))
-			oidcpy(&result.oid, &a->oid);
+			oidcpy(&result->oid, &a->oid);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
 			int merge_status;
@@ -946,64 +946,66 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
 				die(_("Failed to execute internal merge"));
 
 			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result.oid.hash))
+					    blob_type, result->oid.hash))
 				die(_("Unable to add %s to database"),
 				    a->path);
 
 			free(result_buf.ptr);
-			result.clean = (merge_status == 0);
+			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
-			result.clean = merge_submodule(result.oid.hash,
+			result->clean = merge_submodule(result->oid.hash,
 						       one->path,
 						       one->oid.hash,
 						       a->oid.hash,
 						       b->oid.hash,
 						       !o->call_depth);
 		} else if (S_ISLNK(a->mode)) {
-			oidcpy(&result.oid, &a->oid);
+			oidcpy(&result->oid, &a->oid);
 
 			if (!oid_eq(&a->oid, &b->oid))
-				result.clean = 0;
+				result->clean = 0;
 		} else
 			die("BUG: unsupported object type in the tree");
 	}
 
-	return result;
+	return 0;
 }
 
-static struct merge_file_info
-merge_file_special_markers(struct merge_options *o,
+static int merge_file_special_markers(struct merge_options *o,
 			   const struct diff_filespec *one,
 			   const struct diff_filespec *a,
 			   const struct diff_filespec *b,
 			   const char *branch1,
 			   const char *filename1,
 			   const char *branch2,
-			   const char *filename2)
+			   const char *filename2,
+			   struct merge_file_info *mfi)
 {
 	char *side1 = NULL;
 	char *side2 = NULL;
-	struct merge_file_info mfi;
+	int ret;
 
 	if (filename1)
 		side1 = xstrfmt("%s:%s", branch1, filename1);
 	if (filename2)
 		side2 = xstrfmt("%s:%s", branch2, filename2);
 
-	mfi = merge_file_1(o, one, a, b,
-			   side1 ? side1 : branch1, side2 ? side2 : branch2);
+	ret = merge_file_1(o, one, a, b,
+			   side1 ? side1 : branch1,
+			   side2 ? side2 : branch2, mfi);
 	free(side1);
 	free(side2);
-	return mfi;
+	return ret;
 }
 
-static struct merge_file_info merge_file_one(struct merge_options *o,
+static int merge_file_one(struct merge_options *o,
 					 const char *path,
 					 const struct object_id *o_oid, int o_mode,
 					 const struct object_id *a_oid, int a_mode,
 					 const struct object_id *b_oid, int b_mode,
 					 const char *branch1,
-					 const char *branch2)
+					 const char *branch2,
+					 struct merge_file_info *mfi)
 {
 	struct diff_filespec one, a, b;
 
@@ -1014,7 +1016,7 @@ static struct merge_file_info merge_file_one(struct merge_options *o,
 	a.mode = a_mode;
 	oidcpy(&b.oid, b_oid);
 	b.mode = b_mode;
-	return merge_file_1(o, &one, &a, &b, branch1, branch2);
+	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
 static void handle_change_delete(struct merge_options *o,
@@ -1187,11 +1189,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		struct merge_file_info mfi;
 		struct diff_filespec other;
 		struct diff_filespec *add;
-		mfi = merge_file_one(o, one->path,
+		if (merge_file_one(o, one->path,
 				 &one->oid, one->mode,
 				 &a->oid, a->mode,
 				 &b->oid, b->mode,
-				 ci->branch1, ci->branch2);
+				 ci->branch1, ci->branch2, &mfi))
+			return;
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
@@ -1245,12 +1248,13 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 	remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
-	mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
-					    o->branch1, c1->path,
-					    o->branch2, ci->ren1_other.path);
-	mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
-					    o->branch1, ci->ren2_other.path,
-					    o->branch2, c2->path);
+	if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
+				       o->branch1, c1->path,
+				       o->branch2, ci->ren1_other.path, &mfi_c1) ||
+	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
+				       o->branch1, ci->ren2_other.path,
+				       o->branch2, c2->path, &mfi_c2))
+		return;
 
 	if (o->call_depth) {
 		/*
@@ -1473,12 +1477,13 @@ static int process_renames(struct merge_options *o,
 				       ren1_dst, branch2);
 				if (o->call_depth) {
 					struct merge_file_info mfi;
-					mfi = merge_file_one(o, ren1_dst, &null_oid, 0,
-							 &ren1->pair->two->oid,
-							 ren1->pair->two->mode,
-							 &dst_other.oid,
-							 dst_other.mode,
-							 branch1, branch2);
+					if (merge_file_one(o, ren1_dst, &null_oid, 0,
+							   &ren1->pair->two->oid,
+							   ren1->pair->two->mode,
+							   &dst_other.oid,
+							   dst_other.mode,
+							   branch1, branch2, &mfi))
+						return -1;
 					output(o, 1, _("Adding merged %s"), ren1_dst);
 					update_file(o, 0, &mfi.oid,
 						    mfi.mode, ren1_dst);
@@ -1636,9 +1641,10 @@ static int merge_content(struct merge_options *o,
 		if (dir_in_way(path, !o->call_depth))
 			df_conflict_remains = 1;
 	}
-	mfi = merge_file_special_markers(o, &one, &a, &b,
-					 o->branch1, path1,
-					 o->branch2, path2);
+	if (merge_file_special_markers(o, &one, &a, &b,
+				       o->branch1, path1,
+				       o->branch2, path2, &mfi))
+		return -1;
 
 	if (mfi.clean && !df_conflict_remains &&
 	    oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) {
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 08/16] merge-recursive: allow write_tree_from_memory() to error out
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (6 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 07/16] merge-recursive: avoid returning a wholesale struct Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 09/16] merge-recursive: handle return values indicating errors Johannes Schindelin
                           ` (8 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

It is possible that a tree cannot be written (think: disk full). We
will want to give the caller a chance to clean up instead of letting
the program die() in such a case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 2be1e17..1f86338 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1888,8 +1888,8 @@ int merge_trees(struct merge_options *o,
 	else
 		clean = 1;
 
-	if (o->call_depth)
-		*result = write_tree_from_memory(o);
+	if (o->call_depth && !(*result = write_tree_from_memory(o)))
+		return -1;
 
 	return clean;
 }
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 09/16] merge-recursive: handle return values indicating errors
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (7 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 08/16] merge-recursive: allow write_tree_from_memory() to error out Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 10/16] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
                           ` (7 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

We are about to libify the recursive merge machinery, where we only
die() in case of a bug or memory contention. To that end, we must heed
negative return values as indicating errors.

This requires our functions to be careful to pass through error
conditions in call chains, and for quite a few functions this means
that they have to return values to begin with.

The next step will be to convert the places where we currently die() to
return negative values (read: -1) instead.

Note that we ignore errors reported by make_room_for_path(), consistent
with the previous behavior (update_file_flags() used the return value of
make_room_for_path() only to indicate an early return, but not a fatal
error): if the error is really a fatal error, we will notice later; If
not, it was not that serious a problem to begin with. (Witnesses in
favor of this reasoning are t4151-am-abort and t7610-mergetool, which
would start failing if we stopped on errors reported by
make_room_for_path()).

Also note: while this patch makes the code slightly less readable in
update_file_flags() (we introduce a new "goto free_buf;" instead of
an explicit "free(buf); return;"), it is a preparatory change for
the next patch where we will convert all of the die() calls in the same
function to go through the free_buf return path instead.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 252 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 150 insertions(+), 102 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 1f86338..6beb1e4 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -742,12 +742,12 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	return error(msg, path, _(": perhaps a D/F conflict?"));
 }
 
-static void update_file_flags(struct merge_options *o,
-			      const struct object_id *oid,
-			      unsigned mode,
-			      const char *path,
-			      int update_cache,
-			      int update_wd)
+static int update_file_flags(struct merge_options *o,
+			     const struct object_id *oid,
+			     unsigned mode,
+			     const char *path,
+			     int update_cache,
+			     int update_wd)
 {
 	if (o->call_depth)
 		update_wd = 0;
@@ -783,8 +783,7 @@ static void update_file_flags(struct merge_options *o,
 
 		if (make_room_for_path(o, path) < 0) {
 			update_wd = 0;
-			free(buf);
-			goto update_index;
+			goto free_buf;
 		}
 		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
 			int fd;
@@ -807,20 +806,22 @@ static void update_file_flags(struct merge_options *o,
 		} else
 			die(_("do not know what to do with %06o %s '%s'"),
 			    mode, oid_to_hex(oid), path);
+ free_buf:
 		free(buf);
 	}
  update_index:
 	if (update_cache)
 		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+	return 0;
 }
 
-static void update_file(struct merge_options *o,
-			int clean,
-			const struct object_id *oid,
-			unsigned mode,
-			const char *path)
+static int update_file(struct merge_options *o,
+		       int clean,
+		       const struct object_id *oid,
+		       unsigned mode,
+		       const char *path)
 {
-	update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
+	return update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth);
 }
 
 /* Low level file merging, update and removal */
@@ -1019,7 +1020,7 @@ static int merge_file_one(struct merge_options *o,
 	return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
-static void handle_change_delete(struct merge_options *o,
+static int handle_change_delete(struct merge_options *o,
 				 const char *path,
 				 const struct object_id *o_oid, int o_mode,
 				 const struct object_id *a_oid, int a_mode,
@@ -1027,6 +1028,7 @@ static void handle_change_delete(struct merge_options *o,
 				 const char *change, const char *change_past)
 {
 	char *renamed = NULL;
+	int ret = 0;
 	if (dir_in_way(path, !o->call_depth)) {
 		renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2);
 	}
@@ -1037,21 +1039,23 @@ static void handle_change_delete(struct merge_options *o,
 		 * correct; since there is no true "middle point" between
 		 * them, simply reuse the base version for virtual merge base.
 		 */
-		remove_file_from_cache(path);
-		update_file(o, 0, o_oid, o_mode, renamed ? renamed : path);
+		ret = remove_file_from_cache(path);
+		if (!ret)
+			ret = update_file(o, 0, o_oid, o_mode,
+					  renamed ? renamed : path);
 	} else if (!a_oid) {
 		if (!renamed) {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path);
-			update_file(o, 0, b_oid, b_mode, path);
+			ret = update_file(o, 0, b_oid, b_mode, path);
 		} else {
 			output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch1, change_past,
 			       o->branch2, o->branch2, path, renamed);
-			update_file(o, 0, b_oid, b_mode, renamed);
+			ret = update_file(o, 0, b_oid, b_mode, renamed);
 		}
 	} else {
 		if (!renamed) {
@@ -1064,7 +1068,7 @@ static void handle_change_delete(struct merge_options *o,
 			       "and %s in %s. Version %s of %s left in tree at %s."),
 			       change, path, o->branch2, change_past,
 			       o->branch1, o->branch1, path, renamed);
-			update_file(o, 0, a_oid, a_mode, renamed);
+			ret = update_file(o, 0, a_oid, a_mode, renamed);
 		}
 		/*
 		 * No need to call update_file() on path when !renamed, since
@@ -1074,9 +1078,11 @@ static void handle_change_delete(struct merge_options *o,
 		 */
 	}
 	free(renamed);
+
+	return ret;
 }
 
-static void conflict_rename_delete(struct merge_options *o,
+static int conflict_rename_delete(struct merge_options *o,
 				   struct diff_filepair *pair,
 				   const char *rename_branch,
 				   const char *other_branch)
@@ -1096,21 +1102,20 @@ static void conflict_rename_delete(struct merge_options *o,
 		b_mode = dest->mode;
 	}
 
-	handle_change_delete(o,
-			     o->call_depth ? orig->path : dest->path,
-			     &orig->oid, orig->mode,
-			     a_oid, a_mode,
-			     b_oid, b_mode,
-			     _("rename"), _("renamed"));
-
-	if (o->call_depth) {
-		remove_file_from_cache(dest->path);
-	} else {
-		update_stages(dest->path, NULL,
-			      rename_branch == o->branch1 ? dest : NULL,
-			      rename_branch == o->branch1 ? NULL : dest);
-	}
+	if (handle_change_delete(o,
+				 o->call_depth ? orig->path : dest->path,
+				 &orig->oid, orig->mode,
+				 a_oid, a_mode,
+				 b_oid, b_mode,
+				 _("rename"), _("renamed")))
+		return -1;
 
+	if (o->call_depth)
+		return remove_file_from_cache(dest->path);
+	else
+		return update_stages(dest->path, NULL,
+				     rename_branch == o->branch1 ? dest : NULL,
+				     rename_branch == o->branch1 ? NULL : dest);
 }
 
 static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
@@ -1126,7 +1131,7 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 	return target;
 }
 
-static void handle_file(struct merge_options *o,
+static int handle_file(struct merge_options *o,
 			struct diff_filespec *rename,
 			int stage,
 			struct rename_conflict_info *ci)
@@ -1136,6 +1141,7 @@ static void handle_file(struct merge_options *o,
 	const char *cur_branch, *other_branch;
 	struct diff_filespec other;
 	struct diff_filespec *add;
+	int ret;
 
 	if (stage == 2) {
 		dst_entry = ci->dst_entry1;
@@ -1150,7 +1156,8 @@ static void handle_file(struct merge_options *o,
 	add = filespec_from_entry(&other, dst_entry, stage ^ 1);
 	if (add) {
 		char *add_name = unique_path(o, rename->path, other_branch);
-		update_file(o, 0, &add->oid, add->mode, add_name);
+		if (update_file(o, 0, &add->oid, add->mode, add_name))
+			return -1;
 
 		remove_file(o, 0, rename->path, 0);
 		dst_name = unique_path(o, rename->path, cur_branch);
@@ -1161,17 +1168,20 @@ static void handle_file(struct merge_options *o,
 			       rename->path, other_branch, dst_name);
 		}
 	}
-	update_file(o, 0, &rename->oid, rename->mode, dst_name);
-	if (stage == 2)
-		update_stages(rename->path, NULL, rename, add);
+	if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
+		; /* fall through, do allow dst_name to be released */
+	else if (stage == 2)
+		ret = update_stages(rename->path, NULL, rename, add);
 	else
-		update_stages(rename->path, NULL, add, rename);
+		ret = update_stages(rename->path, NULL, add, rename);
 
 	if (dst_name != rename->path)
 		free(dst_name);
+
+	return ret;
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
+static int conflict_rename_rename_1to2(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* One file was renamed in both branches, but to different names. */
@@ -1194,14 +1204,16 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 				 &a->oid, a->mode,
 				 &b->oid, b->mode,
 				 ci->branch1, ci->branch2, &mfi))
-			return;
+			return -1;
+
 		/*
 		 * FIXME: For rename/add-source conflicts (if we could detect
 		 * such), this is wrong.  We should instead find a unique
 		 * pathname and then either rename the add-source file to that
 		 * unique path, or use that unique path instead of src here.
 		 */
-		update_file(o, 0, &mfi.oid, mfi.mode, one->path);
+		if (update_file(o, 0, &mfi.oid, mfi.mode, one->path))
+			return -1;
 
 		/*
 		 * Above, we put the merged content at the merge-base's
@@ -1212,22 +1224,26 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
 		 * resolving the conflict at that path in its favor.
 		 */
 		add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
-		if (add)
-			update_file(o, 0, &add->oid, add->mode, a->path);
+		if (add) {
+			if (update_file(o, 0, &add->oid, add->mode, a->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(a->path);
 		add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
-		if (add)
-			update_file(o, 0, &add->oid, add->mode, b->path);
+		if (add) {
+			if (update_file(o, 0, &add->oid, add->mode, b->path))
+				return -1;
+		}
 		else
 			remove_file_from_cache(b->path);
-	} else {
-		handle_file(o, a, 2, ci);
-		handle_file(o, b, 3, ci);
-	}
+	} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
+		return -1;
+
+	return 0;
 }
 
-static void conflict_rename_rename_2to1(struct merge_options *o,
+static int conflict_rename_rename_2to1(struct merge_options *o,
 					struct rename_conflict_info *ci)
 {
 	/* Two files, a & b, were renamed to the same thing, c. */
@@ -1238,6 +1254,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	char *path = c1->path; /* == c2->path */
 	struct merge_file_info mfi_c1;
 	struct merge_file_info mfi_c2;
+	int ret;
 
 	output(o, 1, _("CONFLICT (rename/rename): "
 	       "Rename %s->%s in %s. "
@@ -1254,7 +1271,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 	    merge_file_special_markers(o, b, &ci->ren2_other, c2,
 				       o->branch1, ci->ren2_other.path,
 				       o->branch2, c2->path, &mfi_c2))
-		return;
+		return -1;
 
 	if (o->call_depth) {
 		/*
@@ -1265,19 +1282,25 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
 		 * again later for the non-recursive merge.
 		 */
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
-		update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, b->path);
+		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
+		if (!ret)
+			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
+					  b->path);
 	} else {
 		char *new_path1 = unique_path(o, path, ci->branch1);
 		char *new_path2 = unique_path(o, path, ci->branch2);
 		output(o, 1, _("Renaming %s to %s and %s to %s instead"),
 		       a->path, new_path1, b->path, new_path2);
 		remove_file(o, 0, path, 0);
-		update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
-		update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, new_path2);
+		ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
+		if (!ret)
+			ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
+					  new_path2);
 		free(new_path2);
 		free(new_path1);
 	}
+
+	return ret;
 }
 
 static int process_renames(struct merge_options *o,
@@ -1462,12 +1485,13 @@ static int process_renames(struct merge_options *o,
 				 * update_file_flags() instead of
 				 * update_file().
 				 */
-				update_file_flags(o,
-						  &ren1->pair->two->oid,
-						  ren1->pair->two->mode,
-						  ren1_dst,
-						  1, /* update_cache */
-						  0  /* update_wd    */);
+				if (update_file_flags(o,
+						      &ren1->pair->two->oid,
+						      ren1->pair->two->mode,
+						      ren1_dst,
+						      1, /* update_cache */
+						      0  /* update_wd    */))
+					clean_merge = -1;
 			} else if (!oid_eq(&dst_other.oid, &null_oid)) {
 				clean_merge = 0;
 				try_merge = 1;
@@ -1482,22 +1506,28 @@ static int process_renames(struct merge_options *o,
 							   ren1->pair->two->mode,
 							   &dst_other.oid,
 							   dst_other.mode,
-							   branch1, branch2, &mfi))
-						return -1;
+							   branch1, branch2, &mfi)) {
+						clean_merge = -1;
+						goto cleanup_and_return;
+					}
 					output(o, 1, _("Adding merged %s"), ren1_dst);
-					update_file(o, 0, &mfi.oid,
-						    mfi.mode, ren1_dst);
+					if (update_file(o, 0, &mfi.oid,
+							mfi.mode, ren1_dst))
+						clean_merge = -1;
 					try_merge = 0;
 				} else {
 					char *new_path = unique_path(o, ren1_dst, branch2);
 					output(o, 1, _("Adding as %s instead"), new_path);
-					update_file(o, 0, &dst_other.oid,
-						    dst_other.mode, new_path);
+					if (update_file(o, 0, &dst_other.oid,
+							dst_other.mode, new_path))
+						clean_merge = -1;
 					free(new_path);
 				}
 			} else
 				try_merge = 1;
 
+			if (clean_merge < 0)
+				goto cleanup_and_return;
 			if (try_merge) {
 				struct diff_filespec *one, *a, *b;
 				src_other.path = (char *)ren1_src;
@@ -1524,6 +1554,7 @@ static int process_renames(struct merge_options *o,
 			}
 		}
 	}
+cleanup_and_return:
 	string_list_clear(&a_by_dst, 0);
 	string_list_clear(&b_by_dst, 0);
 
@@ -1586,18 +1617,18 @@ static int blob_unchanged(const struct object_id *o_oid,
 	return ret;
 }
 
-static void handle_modify_delete(struct merge_options *o,
+static int handle_modify_delete(struct merge_options *o,
 				 const char *path,
 				 struct object_id *o_oid, int o_mode,
 				 struct object_id *a_oid, int a_mode,
 				 struct object_id *b_oid, int b_mode)
 {
-	handle_change_delete(o,
-			     path,
-			     o_oid, o_mode,
-			     a_oid, a_mode,
-			     b_oid, b_mode,
-			     _("modify"), _("modified"));
+	return handle_change_delete(o,
+				    path,
+				    o_oid, o_mode,
+				    a_oid, a_mode,
+				    b_oid, b_mode,
+				    _("modify"), _("modified"));
 }
 
 static int merge_content(struct merge_options *o,
@@ -1671,7 +1702,8 @@ static int merge_content(struct merge_options *o,
 		output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
 				reason, path);
 		if (rename_conflict_info && !df_conflict_remains)
-			update_stages(path, &one, &a, &b);
+			if (update_stages(path, &one, &a, &b))
+				return -1;
 	}
 
 	if (df_conflict_remains) {
@@ -1679,30 +1711,33 @@ static int merge_content(struct merge_options *o,
 		if (o->call_depth) {
 			remove_file_from_cache(path);
 		} else {
-			if (!mfi.clean)
-				update_stages(path, &one, &a, &b);
-			else {
+			if (!mfi.clean) {
+				if (update_stages(path, &one, &a, &b))
+					return -1;
+			} else {
 				int file_from_stage2 = was_tracked(path);
 				struct diff_filespec merged;
 				oidcpy(&merged.oid, &mfi.oid);
 				merged.mode = mfi.mode;
 
-				update_stages(path, NULL,
-					      file_from_stage2 ? &merged : NULL,
-					      file_from_stage2 ? NULL : &merged);
+				if (update_stages(path, NULL,
+						  file_from_stage2 ? &merged : NULL,
+						  file_from_stage2 ? NULL : &merged))
+					return -1;
 			}
 
 		}
 		new_path = unique_path(o, path, rename_conflict_info->branch1);
 		output(o, 1, _("Adding as %s instead"), new_path);
-		update_file(o, 0, &mfi.oid, mfi.mode, new_path);
+		if (update_file(o, 0, &mfi.oid, mfi.mode, new_path)) {
+			free(new_path);
+			return -1;
+		}
 		free(new_path);
 		mfi.clean = 0;
-	} else {
-		update_file(o, mfi.clean, &mfi.oid, mfi.mode, path);
-	}
+	} else if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, path))
+		return -1;
 	return mfi.clean;
-
 }
 
 /* Per entry merge function */
@@ -1730,17 +1765,21 @@ static int process_entry(struct merge_options *o,
 			break;
 		case RENAME_DELETE:
 			clean_merge = 0;
-			conflict_rename_delete(o, conflict_info->pair1,
-					       conflict_info->branch1,
-					       conflict_info->branch2);
+			if (conflict_rename_delete(o,
+						   conflict_info->pair1,
+						   conflict_info->branch1,
+						   conflict_info->branch2))
+				clean_merge = -1;
 			break;
 		case RENAME_ONE_FILE_TO_TWO:
 			clean_merge = 0;
-			conflict_rename_rename_1to2(o, conflict_info);
+			if (conflict_rename_rename_1to2(o, conflict_info))
+				clean_merge = -1;
 			break;
 		case RENAME_TWO_FILES_TO_ONE:
 			clean_merge = 0;
-			conflict_rename_rename_2to1(o, conflict_info);
+			if (conflict_rename_rename_2to1(o, conflict_info))
+				clean_merge = -1;
 			break;
 		default:
 			entry->processed = 0;
@@ -1760,8 +1799,9 @@ static int process_entry(struct merge_options *o,
 		} else {
 			/* Modify/delete; deleted side may have put a directory in the way */
 			clean_merge = 0;
-			handle_modify_delete(o, path, o_oid, o_mode,
-					     a_oid, a_mode, b_oid, b_mode);
+			if (handle_modify_delete(o, path, o_oid, o_mode,
+						 a_oid, a_mode, b_oid, b_mode))
+				clean_merge = -1;
 		}
 	} else if ((!o_oid && a_oid && !b_oid) ||
 		   (!o_oid && !a_oid && b_oid)) {
@@ -1793,14 +1833,16 @@ static int process_entry(struct merge_options *o,
 			output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
 			       "Adding %s as %s"),
 			       conf, path, other_branch, path, new_path);
-			update_file(o, 0, oid, mode, new_path);
-			if (o->call_depth)
+			if (update_file(o, 0, oid, mode, new_path))
+				clean_merge = -1;
+			else if (o->call_depth)
 				remove_file_from_cache(path);
 			free(new_path);
 		} else {
 			output(o, 2, _("Adding %s"), path);
 			/* do not overwrite file if already present */
-			update_file_flags(o, oid, mode, path, 1, !a_oid);
+			if (update_file_flags(o, oid, mode, path, 1, !a_oid))
+				clean_merge = -1;
 		}
 	} else if (a_oid && b_oid) {
 		/* Case C: Added in both (check for same permissions) and */
@@ -1863,12 +1905,18 @@ int merge_trees(struct merge_options *o,
 		re_head  = get_renames(o, head, common, head, merge, entries);
 		re_merge = get_renames(o, merge, common, head, merge, entries);
 		clean = process_renames(o, re_head, re_merge);
+		if (clean < 0)
+			return clean;
 		for (i = entries->nr-1; 0 <= i; i--) {
 			const char *path = entries->items[i].string;
 			struct stage_data *e = entries->items[i].util;
-			if (!e->processed
-				&& !process_entry(o, path, e))
-				clean = 0;
+			if (!e->processed) {
+				int ret = process_entry(o, path, e);
+				if (!ret)
+					clean = 0;
+				else if (ret < 0)
+					return ret;
+			}
 		}
 		for (i = 0; i < entries->nr; i++) {
 			struct stage_data *e = entries->items[i].util;
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 10/16] merge-recursive: switch to returning errors instead of dying
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (8 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 09/16] merge-recursive: handle return values indicating errors Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
                           ` (6 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The recursive merge machinery is supposed to be a library function, i.e.
it should return an error when it fails. Originally the functions were
part of the builtin "merge-recursive", though, where it was simpler to
call die() and be done with error handling.

The existing callers were already prepared to detect negative return
values to indicate errors and to behave as previously: exit with code 128
(which is the same thing that die() does, after printing the message).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 62 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 35 insertions(+), 27 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 6beb1e4..bc59815 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -275,8 +275,10 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 		active_cache_tree = cache_tree();
 
 	if (!cache_tree_fully_valid(active_cache_tree) &&
-	    cache_tree_update(&the_index, 0) < 0)
-		die(_("error building trees"));
+	    cache_tree_update(&the_index, 0) < 0) {
+		error(_("error building trees"));
+		return NULL;
+	}
 
 	result = lookup_tree(active_cache_tree->sha1);
 
@@ -716,12 +718,10 @@ static int make_room_for_path(struct merge_options *o, const char *path)
 	/* Make sure leading directories are created */
 	status = safe_create_leading_directories_const(path);
 	if (status) {
-		if (status == SCLD_EXISTS) {
+		if (status == SCLD_EXISTS)
 			/* something else exists */
-			error(msg, path, _(": perhaps a D/F conflict?"));
-			return -1;
-		}
-		die(msg, path, "");
+			return error(msg, path, _(": perhaps a D/F conflict?"));
+		return error(msg, path, "");
 	}
 
 	/*
@@ -749,6 +749,8 @@ static int update_file_flags(struct merge_options *o,
 			     int update_cache,
 			     int update_wd)
 {
+	int ret = 0;
+
 	if (o->call_depth)
 		update_wd = 0;
 
@@ -769,9 +771,11 @@ static int update_file_flags(struct merge_options *o,
 
 		buf = read_sha1_file(oid->hash, &type, &size);
 		if (!buf)
-			die(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
-		if (type != OBJ_BLOB)
-			die(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			return error(_("cannot read object %s '%s'"), oid_to_hex(oid), path);
+		if (type != OBJ_BLOB) {
+			ret = error(_("blob expected for %s '%s'"), oid_to_hex(oid), path);
+			goto free_buf;
+		}
 		if (S_ISREG(mode)) {
 			struct strbuf strbuf = STRBUF_INIT;
 			if (convert_to_working_tree(path, buf, size, &strbuf)) {
@@ -792,8 +796,11 @@ static int update_file_flags(struct merge_options *o,
 			else
 				mode = 0666;
 			fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-			if (fd < 0)
-				die_errno(_("failed to open '%s'"), path);
+			if (fd < 0) {
+				ret = error_errno(_("failed to open '%s'"),
+						  path);
+				goto free_buf;
+			}
 			write_in_full(fd, buf, size);
 			close(fd);
 		} else if (S_ISLNK(mode)) {
@@ -801,18 +808,18 @@ static int update_file_flags(struct merge_options *o,
 			safe_create_leading_directories_const(path);
 			unlink(path);
 			if (symlink(lnk, path))
-				die_errno(_("failed to symlink '%s'"), path);
+				ret = error_errno(_("failed to symlink '%s'"), path);
 			free(lnk);
 		} else
-			die(_("do not know what to do with %06o %s '%s'"),
-			    mode, oid_to_hex(oid), path);
+			ret = error(_("do not know what to do with %06o %s '%s'"),
+				    mode, oid_to_hex(oid), path);
  free_buf:
 		free(buf);
 	}
  update_index:
-	if (update_cache)
+	if (!ret && update_cache)
 		add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-	return 0;
+	return ret;
 }
 
 static int update_file(struct merge_options *o,
@@ -938,20 +945,22 @@ static int merge_file_1(struct merge_options *o,
 			oidcpy(&result->oid, &a->oid);
 		else if (S_ISREG(a->mode)) {
 			mmbuffer_t result_buf;
-			int merge_status;
+			int ret = 0, merge_status;
 
 			merge_status = merge_3way(o, &result_buf, one, a, b,
 						  branch1, branch2);
 
 			if ((merge_status < 0) || !result_buf.ptr)
-				die(_("Failed to execute internal merge"));
+				ret = error(_("Failed to execute internal merge"));
 
-			if (write_sha1_file(result_buf.ptr, result_buf.size,
-					    blob_type, result->oid.hash))
-				die(_("Unable to add %s to database"),
-				    a->path);
+			if (!ret && write_sha1_file(result_buf.ptr, result_buf.size,
+						    blob_type, result->oid.hash))
+				ret = error(_("Unable to add %s to database"),
+					    a->path);
 
 			free(result_buf.ptr);
+			if (ret)
+				return ret;
 			result->clean = (merge_status == 0);
 		} else if (S_ISGITLINK(a->mode)) {
 			result->clean = merge_submodule(result->oid.hash,
@@ -1885,11 +1894,10 @@ int merge_trees(struct merge_options *o,
 
 	if (code != 0) {
 		if (show(o, 4) || o->call_depth)
-			die(_("merging of trees %s and %s failed"),
+			error(_("merging of trees %s and %s failed"),
 			    oid_to_hex(&head->object.oid),
 			    oid_to_hex(&merge->object.oid));
-		else
-			exit(128);
+		return -1;
 	}
 
 	if (unmerged_cache()) {
@@ -2021,7 +2029,7 @@ int merge_recursive(struct merge_options *o,
 		o->call_depth--;
 
 		if (!merged_common_ancestors)
-			die(_("merge returned no commit"));
+			return error(_("merge returned no commit"));
 	}
 
 	discard_cache();
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 11/16] am -3: use merge_recursive() directly again
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (9 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 10/16] merge-recursive: switch to returning errors instead of dying Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-26 16:06         ` [PATCH v5 12/16] merge-recursive: flush output buffer before printing error messages Johannes Schindelin
                           ` (5 subsequent siblings)
  16 siblings, 0 replies; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

Last October, we had to change this code to run `git merge-recursive`
in a child process: git-am wants to print some helpful advice when the
merge failed, but the code in question was not prepared to return, it
die()d instead.

We are finally at a point when the code *is* prepared to return errors,
and can avoid the child process again.

This reverts commit c63d4b2 (am -3: do not let failed merge from
completing the error codepath, 2015-10-09), with the necessary changes
to adjust for the fact that Git's source code changed in the meantime
(such as: using OIDs instead of hashes in the recursive merge, and a
removed gender bias).

Note: the code now calls merge_recursive_generic() again. Unlike
merge_trees() and merge_recursive(), this function returns 0 upon success,
as most of Git's functions. Therefore, the error value -1 naturally is
handled correctly, and we do not have to take care of it specifically.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/am.c | 62 +++++++++++++++++++++---------------------------------------
 1 file changed, 22 insertions(+), 40 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index b77bf11..cfb79ea 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1579,47 +1579,18 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 }
 
 /**
- * Do the three-way merge using fake ancestor, their tree constructed
- * from the fake ancestor and the postimage of the patch, and our
- * state.
- */
-static int run_fallback_merge_recursive(const struct am_state *state,
-					unsigned char *orig_tree,
-					unsigned char *our_tree,
-					unsigned char *their_tree)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-	int status;
-
-	cp.git_cmd = 1;
-
-	argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
-			 sha1_to_hex(their_tree), linelen(state->msg), state->msg);
-	if (state->quiet)
-		argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
-
-	argv_array_push(&cp.args, "merge-recursive");
-	argv_array_push(&cp.args, sha1_to_hex(orig_tree));
-	argv_array_push(&cp.args, "--");
-	argv_array_push(&cp.args, sha1_to_hex(our_tree));
-	argv_array_push(&cp.args, sha1_to_hex(their_tree));
-
-	status = run_command(&cp) ? (-1) : 0;
-	discard_cache();
-	read_cache();
-	return status;
-}
-
-/**
  * Attempt a threeway merge, using index_path as the temporary index.
  */
 static int fall_back_threeway(const struct am_state *state, const char *index_path)
 {
-	unsigned char orig_tree[GIT_SHA1_RAWSZ], their_tree[GIT_SHA1_RAWSZ],
-		      our_tree[GIT_SHA1_RAWSZ];
+	struct object_id orig_tree, their_tree, our_tree;
+	const struct object_id *bases[1] = { &orig_tree };
+	struct merge_options o;
+	struct commit *result;
+	char *their_tree_name;
 
-	if (get_sha1("HEAD", our_tree) < 0)
-		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
+	if (get_oid("HEAD", &our_tree) < 0)
+		hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
 
 	if (build_fake_ancestor(state, index_path))
 		return error("could not build fake ancestor");
@@ -1627,7 +1598,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	discard_cache();
 	read_cache_from(index_path);
 
-	if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
 		return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
 
 	say(state, stdout, _("Using index info to reconstruct a base tree..."));
@@ -1643,7 +1614,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		init_revisions(&rev_info, NULL);
 		rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
 		diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
-		add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
+		add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
 		diff_setup_done(&rev_info.diffopt);
 		run_diff_index(&rev_info, 1);
 	}
@@ -1652,7 +1623,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		return error(_("Did you hand edit your patch?\n"
 				"It does not apply to blobs recorded in its index."));
 
-	if (write_index_as_tree(their_tree, &the_index, index_path, 0, NULL))
+	if (write_index_as_tree(their_tree.hash, &the_index, index_path, 0, NULL))
 		return error("could not write tree");
 
 	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
@@ -1668,11 +1639,22 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 	 * changes.
 	 */
 
-	if (run_fallback_merge_recursive(state, orig_tree, our_tree, their_tree)) {
+	init_merge_options(&o);
+
+	o.branch1 = "HEAD";
+	their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+	o.branch2 = their_tree_name;
+
+	if (state->quiet)
+		o.verbosity = 0;
+
+	if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
 		rerere(state->allow_rerere_autoupdate);
+		free(their_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
 
+	free(their_tree_name);
 	return 0;
 }
 
-- 
2.9.0.281.g286a8d9



^ permalink raw reply related	[flat|nested] 262+ messages in thread

* [PATCH v5 12/16] merge-recursive: flush output buffer before printing error messages
  2016-07-26 16:05       ` [PATCH v5 00/16] Use merge_recursive() directly in the builtin am Johannes Schindelin
                           ` (10 preceding siblings ...)
  2016-07-26 16:06         ` [PATCH v5 11/16] am -3: use merge_recursive() directly again Johannes Schindelin
@ 2016-07-26 16:06         ` Johannes Schindelin
  2016-07-27 21:37           ` Junio C Hamano
  2016-07-26 16:06         ` [PATCH v5 13/16] merge-recursive: write the commit title in one go Johannes Schindelin
                           ` (4 subsequent siblings)
  16 siblings, 1 reply; 262+ messages in thread
From: Johannes Schindelin @ 2016-07-26 16:06 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Eric Sunshine, Jeff King, Johannes Sixt,
	Duy Nguyen, Jakub Narębski

The data structure passed to the recursive merge machinery has a feature
where the caller can ask for the output to be buffered into a strbuf, by
setting the field 'buffer_output'.

Previously, we simply swallowed the buffered output when showing error
messages. With this patch, we show the output first, and only then print
the error message.

Currently, the only user of that buffering is merge_recursive() itself,
to avoid the progress output to interfere.

In the next patches, we will introduce a new buffer_output mode that
forces merge_recursive() to retain the output buffer for further
processing by the caller. If the caller asked for that, we will then
also write the error messages into the output buffer. This is necessary
to give the caller more control not only how to react in case of errors
but also control how/if to display the error messages.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 merge-recursive.c | 116 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 68 insertions(+), 48 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index bc59815..71a0aa0 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -23,6