static int do_merge()

in sequencer.c [3809:4123]


static int do_merge(struct repository *r,
		    struct commit *commit,
		    const char *arg, int arg_len,
		    int flags, int *check_todo, struct replay_opts *opts)
{
	int run_commit_flags = 0;
	struct strbuf ref_name = STRBUF_INIT;
	struct commit *head_commit, *merge_commit, *i;
	struct commit_list *bases, *j;
	struct commit_list *to_merge = NULL, **tail = &to_merge;
	const char *strategy = !opts->xopts_nr &&
		(!opts->strategy ||
		 !strcmp(opts->strategy, "recursive") ||
		 !strcmp(opts->strategy, "ort")) ?
		NULL : opts->strategy;
	struct merge_options o;
	int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
	static struct lock_file lock;
	const char *p;

	if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
		ret = -1;
		goto leave_merge;
	}

	head_commit = lookup_commit_reference_by_name("HEAD");
	if (!head_commit) {
		ret = error(_("cannot merge without a current revision"));
		goto leave_merge;
	}

	/*
	 * For octopus merges, the arg starts with the list of revisions to be
	 * merged. The list is optionally followed by '#' and the oneline.
	 */
	merge_arg_len = oneline_offset = arg_len;
	for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
		if (!*p)
			break;
		if (*p == '#' && (!p[1] || isspace(p[1]))) {
			p += 1 + strspn(p + 1, " \t\n");
			oneline_offset = p - arg;
			break;
		}
		k = strcspn(p, " \t\n");
		if (!k)
			continue;
		merge_commit = lookup_label(p, k, &ref_name);
		if (!merge_commit) {
			ret = error(_("unable to parse '%.*s'"), k, p);
			goto leave_merge;
		}
		tail = &commit_list_insert(merge_commit, tail)->next;
		p += k;
		merge_arg_len = p - arg;
	}

	if (!to_merge) {
		ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
		goto leave_merge;
	}

	if (opts->have_squash_onto &&
	    oideq(&head_commit->object.oid, &opts->squash_onto)) {
		/*
		 * When the user tells us to "merge" something into a
		 * "[new root]", let's simply fast-forward to the merge head.
		 */
		rollback_lock_file(&lock);
		if (to_merge->next)
			ret = error(_("octopus merge cannot be executed on "
				      "top of a [new root]"));
		else
			ret = fast_forward_to(r, &to_merge->item->object.oid,
					      &head_commit->object.oid, 0,
					      opts);
		goto leave_merge;
	}

	/*
	 * If HEAD is not identical to the first parent of the original merge
	 * commit, we cannot fast-forward.
	 */
	can_fast_forward = opts->allow_ff && commit && commit->parents &&
		oideq(&commit->parents->item->object.oid,
		      &head_commit->object.oid);

	/*
	 * If any merge head is different from the original one, we cannot
	 * fast-forward.
	 */
	if (can_fast_forward) {
		struct commit_list *p = commit->parents->next;

		for (j = to_merge; j && p; j = j->next, p = p->next)
			if (!oideq(&j->item->object.oid,
				   &p->item->object.oid)) {
				can_fast_forward = 0;
				break;
			}
		/*
		 * If the number of merge heads differs from the original merge
		 * commit, we cannot fast-forward.
		 */
		if (j || p)
			can_fast_forward = 0;
	}

	if (can_fast_forward) {
		rollback_lock_file(&lock);
		ret = fast_forward_to(r, &commit->object.oid,
				      &head_commit->object.oid, 0, opts);
		if (flags & TODO_EDIT_MERGE_MSG)
			goto fast_forward_edit;

		goto leave_merge;
	}

	if (commit) {
		const char *encoding = get_commit_output_encoding();
		const char *message = logmsg_reencode(commit, NULL, encoding);
		const char *body;
		int len;

		if (!message) {
			ret = error(_("could not get commit message of '%s'"),
				    oid_to_hex(&commit->object.oid));
			goto leave_merge;
		}
		write_author_script(message);
		find_commit_subject(message, &body);
		len = strlen(body);
		ret = write_message(body, len, git_path_merge_msg(r), 0);
		unuse_commit_buffer(commit, message);
		if (ret) {
			error_errno(_("could not write '%s'"),
				    git_path_merge_msg(r));
			goto leave_merge;
		}
	} else {
		struct strbuf buf = STRBUF_INIT;
		int len;

		strbuf_addf(&buf, "author %s", git_author_info(0));
		write_author_script(buf.buf);
		strbuf_reset(&buf);

		if (oneline_offset < arg_len) {
			p = arg + oneline_offset;
			len = arg_len - oneline_offset;
		} else {
			strbuf_addf(&buf, "Merge %s '%.*s'",
				    to_merge->next ? "branches" : "branch",
				    merge_arg_len, arg);
			p = buf.buf;
			len = buf.len;
		}

		ret = write_message(p, len, git_path_merge_msg(r), 0);
		strbuf_release(&buf);
		if (ret) {
			error_errno(_("could not write '%s'"),
				    git_path_merge_msg(r));
			goto leave_merge;
		}
	}

	if (strategy || to_merge->next) {
		/* Octopus merge */
		struct child_process cmd = CHILD_PROCESS_INIT;

		if (read_env_script(&cmd.env)) {
			const char *gpg_opt = gpg_sign_opt_quoted(opts);

			ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
			goto leave_merge;
		}

		if (opts->committer_date_is_author_date)
			strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
				     opts->ignore_date ?
				     "" :
				     author_date_from_env(&cmd.env));
		if (opts->ignore_date)
			strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");

		cmd.git_cmd = 1;
		strvec_push(&cmd.args, "merge");
		strvec_push(&cmd.args, "-s");
		if (!strategy)
			strvec_push(&cmd.args, "octopus");
		else {
			strvec_push(&cmd.args, strategy);
			for (k = 0; k < opts->xopts_nr; k++)
				strvec_pushf(&cmd.args,
					     "-X%s", opts->xopts[k]);
		}
		if (!(flags & TODO_EDIT_MERGE_MSG))
			strvec_push(&cmd.args, "--no-edit");
		else
			strvec_push(&cmd.args, "--edit");
		strvec_push(&cmd.args, "--no-ff");
		strvec_push(&cmd.args, "--no-log");
		strvec_push(&cmd.args, "--no-stat");
		strvec_push(&cmd.args, "-F");
		strvec_push(&cmd.args, git_path_merge_msg(r));
		if (opts->gpg_sign)
			strvec_pushf(&cmd.args, "-S%s", opts->gpg_sign);
		else
			strvec_push(&cmd.args, "--no-gpg-sign");

		/* Add the tips to be merged */
		for (j = to_merge; j; j = j->next)
			strvec_push(&cmd.args,
				    oid_to_hex(&j->item->object.oid));

		strbuf_release(&ref_name);
		refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
				NULL, 0);
		rollback_lock_file(&lock);

		ret = run_command(&cmd);

		/* force re-reading of the cache */
		if (!ret && (discard_index(r->index) < 0 ||
			     repo_read_index(r) < 0))
			ret = error(_("could not read index"));
		goto leave_merge;
	}

	merge_commit = to_merge->item;
	bases = get_merge_bases(head_commit, merge_commit);
	if (bases && oideq(&merge_commit->object.oid,
			   &bases->item->object.oid)) {
		ret = 0;
		/* skip merging an ancestor of HEAD */
		goto leave_merge;
	}

	write_message(oid_to_hex(&merge_commit->object.oid), the_hash_algo->hexsz,
		      git_path_merge_head(r), 0);
	write_message("no-ff", 5, git_path_merge_mode(r), 0);

	bases = reverse_commit_list(bases);

	repo_read_index(r);
	init_merge_options(&o, r);
	o.branch1 = "HEAD";
	o.branch2 = ref_name.buf;
	o.buffer_output = 2;

	if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
		/*
		 * TODO: Should use merge_incore_recursive() and
		 * merge_switch_to_result(), skipping the call to
		 * merge_switch_to_result() when we don't actually need to
		 * update the index and working copy immediately.
		 */
		ret = merge_ort_recursive(&o,
					  head_commit, merge_commit, bases,
					  &i);
	} else {
		ret = merge_recursive(&o, head_commit, merge_commit, bases,
				      &i);
	}
	if (ret <= 0)
		fputs(o.obuf.buf, stdout);
	strbuf_release(&o.obuf);
	if (ret < 0) {
		error(_("could not even attempt to merge '%.*s'"),
		      merge_arg_len, arg);
		goto leave_merge;
	}
	/*
	 * The return value of merge_recursive() is 1 on clean, and 0 on
	 * unclean merge.
	 *
	 * Let's reverse that, so that do_merge() returns 0 upon success and
	 * 1 upon failed merge (keeping the return value -1 for the cases where
	 * we will want to reschedule the `merge` command).
	 */
	ret = !ret;

	if (r->index->cache_changed &&
	    write_locked_index(r->index, &lock, COMMIT_LOCK)) {
		ret = error(_("merge: Unable to write new index file"));
		goto leave_merge;
	}

	rollback_lock_file(&lock);
	if (ret)
		repo_rerere(r, opts->allow_rerere_auto);
	else
		/*
		 * In case of problems, we now want to return a positive
		 * value (a negative one would indicate that the `merge`
		 * command needs to be rescheduled).
		 */
		ret = !!run_git_commit(git_path_merge_msg(r), opts,
				       run_commit_flags);

	if (!ret && flags & TODO_EDIT_MERGE_MSG) {
	fast_forward_edit:
		*check_todo = 1;
		run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
		ret = !!run_git_commit(NULL, opts, run_commit_flags);
	}


leave_merge:
	strbuf_release(&ref_name);
	rollback_lock_file(&lock);
	free_commit_list(to_merge);
	return ret;
}