提交 1c42d760 编写于 作者: J Junio C Hamano

Merge branch 'ag/merge-strategies-in-c' into seen

The resolve and octopus merge strategy backends have been rewritten
in C.

* ag/merge-strategies-in-c:
  sequencer: use the "octopus" merge strategy without forking
  sequencer: use the "resolve" strategy without forking
  merge: use the "octopus" strategy without forking
  merge: use the "resolve" strategy without forking
  merge-octopus: rewrite in C
  merge-recursive: move better_branch_name() to merge.c
  merge-resolve: rewrite in C
  merge-index: don't fork if the requested program is `git-merge-one-file'
  merge-index: libify merge_one_path() and merge_all()
  merge-one-file: rewrite in C
  t6027: modernise tests
......@@ -603,9 +603,6 @@ unexport CDPATH
SCRIPT_SH += git-bisect.sh
SCRIPT_SH += git-difftool--helper.sh
SCRIPT_SH += git-filter-branch.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-request-pull.sh
......@@ -924,6 +921,7 @@ LIB_OBJS += match-trees.o
LIB_OBJS += mem-pool.o
LIB_OBJS += merge-blobs.o
LIB_OBJS += merge-recursive.o
LIB_OBJS += merge-strategies.o
LIB_OBJS += merge.o
LIB_OBJS += mergesort.o
LIB_OBJS += midx.o
......@@ -1115,8 +1113,11 @@ BUILTIN_OBJS += builtin/mailsplit.o
BUILTIN_OBJS += builtin/merge-base.o
BUILTIN_OBJS += builtin/merge-file.o
BUILTIN_OBJS += builtin/merge-index.o
BUILTIN_OBJS += builtin/merge-octopus.o
BUILTIN_OBJS += builtin/merge-one-file.o
BUILTIN_OBJS += builtin/merge-ours.o
BUILTIN_OBJS += builtin/merge-recursive.o
BUILTIN_OBJS += builtin/merge-resolve.o
BUILTIN_OBJS += builtin/merge-tree.o
BUILTIN_OBJS += builtin/merge.o
BUILTIN_OBJS += builtin/mktag.o
......
......@@ -179,9 +179,12 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);
int cmd_merge(int argc, const char **argv, const char *prefix);
int cmd_merge_base(int argc, const char **argv, const char *prefix);
int cmd_merge_index(int argc, const char **argv, const char *prefix);
int cmd_merge_octopus(int argc, const char **argv, const char *prefix);
int cmd_merge_ours(int argc, const char **argv, const char *prefix);
int cmd_merge_file(int argc, const char **argv, const char *prefix);
int cmd_merge_one_file(int argc, const char **argv, const char *prefix);
int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
int cmd_merge_resolve(int argc, const char **argv, const char *prefix);
int cmd_merge_tree(int argc, const char **argv, const char *prefix);
int cmd_mktag(int argc, const char **argv, const char *prefix);
int cmd_mktree(int argc, const char **argv, const char *prefix);
......
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "run-command.h"
static const char *pgm;
static int one_shot, quiet;
static int err;
static int merge_entry(int pos, const char *path)
{
int found;
const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
char hexbuf[4][GIT_MAX_HEXSZ + 1];
char ownbuf[4][60];
if (pos >= active_nr)
die("git merge-index: %s not in the cache", path);
found = 0;
do {
const struct cache_entry *ce = active_cache[pos];
int stage = ce_stage(ce);
if (strcmp(ce->name, path))
break;
found++;
oid_to_hex_r(hexbuf[stage], &ce->oid);
xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode);
arguments[stage] = hexbuf[stage];
arguments[stage + 4] = ownbuf[stage];
} while (++pos < active_nr);
if (!found)
die("git merge-index: %s not in the cache", path);
if (run_command_v_opt(arguments, 0)) {
if (one_shot)
err++;
else {
if (!quiet)
die("merge program failed");
exit(1);
}
}
return found;
}
static void merge_one_path(const char *path)
{
int pos = cache_name_pos(path, strlen(path));
/*
* If it already exists in the cache as stage0, it's
* already merged and there is nothing to do.
*/
if (pos < 0)
merge_entry(-pos-1, path);
}
static void merge_all(void)
{
int i;
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
i += merge_entry(i, ce->name)-1;
}
}
#include "lockfile.h"
#include "merge-strategies.h"
int cmd_merge_index(int argc, const char **argv, const char *prefix)
{
int i, force_file = 0;
int i, force_file = 0, err = 0, one_shot = 0, quiet = 0;
const char *pgm;
void *data;
merge_cb merge_action;
struct lock_file lock = LOCK_INIT;
/* Without this we cannot rely on waitpid() to tell
* what happened to our children.
......@@ -89,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
quiet = 1;
i++;
}
pgm = argv[i++];
if (!strcmp(pgm, "git-merge-one-file")) {
merge_action = merge_one_file_cb;
data = (void *)the_repository;
setup_work_tree();
hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
} else {
merge_action = merge_program_cb;
data = (void *)pgm;
}
for (; i < argc; i++) {
const char *arg = argv[i];
if (!force_file && *arg == '-') {
......@@ -98,14 +51,23 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "-a")) {
merge_all();
err |= merge_all(&the_index, one_shot, quiet,
merge_action, data);
continue;
}
die("git merge-index: unknown option %s", arg);
}
merge_one_path(arg);
err |= merge_one_path(&the_index, one_shot, quiet, arg,
merge_action, data);
}
if (merge_action == merge_one_file_cb) {
if (err) {
rollback_lock_file(&lock);
return err;
}
return write_locked_index(&the_index, &lock, COMMIT_LOCK);
}
if (err && !quiet)
die("merge program failed");
return err;
}
/*
* Builtin "git merge-octopus"
*
* Copyright (c) 2020 Alban Gruin
*
* Based on git-merge-octopus.sh, written by Junio C Hamano.
*
* Resolve two or more trees.
*/
#include "cache.h"
#include "builtin.h"
#include "commit.h"
#include "merge-strategies.h"
static const char builtin_merge_octopus_usage[] =
"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]";
int cmd_merge_octopus(int argc, const char **argv, const char *prefix)
{
int i, sep_seen = 0;
struct commit_list *bases = NULL, *remotes = NULL;
struct commit_list **next_base = &bases, **next_remote = &remotes;
const char *head_arg = NULL;
if (argc < 5)
usage(builtin_merge_octopus_usage);
setup_work_tree();
if (repo_read_index(the_repository) < 0)
die("corrupted cache");
/*
* The first parameters up to -- are merge bases; the rest are
* heads.
*/
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--") == 0)
sep_seen = 1;
else if (strcmp(argv[i], "-h") == 0)
usage(builtin_merge_octopus_usage);
else if (sep_seen && !head_arg)
head_arg = argv[i];
else {
struct object_id oid;
get_oid(argv[i], &oid);
if (!oideq(&oid, the_hash_algo->empty_tree)) {
struct commit *commit;
commit = lookup_commit_or_die(&oid, argv[i]);
if (sep_seen)
next_remote = commit_list_append(commit, next_remote);
else
next_base = commit_list_append(commit, next_base);
}
}
}
/*
* Reject if this is not an octopus -- resolve should be used
* instead.
*/
if (commit_list_count(remotes) < 2)
return 2;
return merge_strategies_octopus(the_repository, bases, head_arg, remotes);
}
/*
* Builtin "git merge-one-file"
*
* Copyright (c) 2020 Alban Gruin
*
* Based on git-merge-one-file.sh, written by Linus Torvalds.
*
* This is the git per-file merge utility, called with
*
* argv[1] - original file SHA1 (or empty)
* argv[2] - file in branch1 SHA1 (or empty)
* argv[3] - file in branch2 SHA1 (or empty)
* argv[4] - pathname in repository
* argv[5] - original file mode (or empty)
* argv[6] - file in branch1 mode (or empty)
* argv[7] - file in branch2 mode (or empty)
*
* Handle some trivial cases. The _really_ trivial cases have been
* handled already by git read-tree, but that one doesn't do any merges
* that might change the tree layout.
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "builtin.h"
#include "lockfile.h"
#include "merge-strategies.h"
static const char builtin_merge_one_file_usage[] =
"git merge-one-file <orig blob> <our blob> <their blob> <path> "
"<orig mode> <our mode> <their mode>\n\n"
"Blob ids and modes should be empty for missing files.";
static int read_mode(const char *name, const char *arg, unsigned int *mode)
{
char *last;
int ret = 0;
*mode = strtol(arg, &last, 8);
if (*last)
ret = error(_("invalid '%s' mode: expected nothing, got '%c'"), name, *last);
else if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))
ret = error(_("invalid '%s' mode: %o"), name, *mode);
return ret;
}
int cmd_merge_one_file(int argc, const char **argv, const char *prefix)
{
struct object_id orig_blob, our_blob, their_blob,
*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;
unsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;
struct lock_file lock = LOCK_INIT;
if (argc != 8)
usage(builtin_merge_one_file_usage);
if (read_cache() < 0)
die("invalid index");
hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
if (!get_oid(argv[1], &orig_blob)) {
p_orig_blob = &orig_blob;
ret = read_mode("orig", argv[5], &orig_mode);
}
if (!get_oid(argv[2], &our_blob)) {
p_our_blob = &our_blob;
ret = read_mode("our", argv[6], &our_mode);
}
if (!get_oid(argv[3], &their_blob)) {
p_their_blob = &their_blob;
ret = read_mode("their", argv[7], &their_mode);
}
if (ret)
return ret;
ret = merge_strategies_one_file(the_repository,
p_orig_blob, p_our_blob, p_their_blob, argv[4],
orig_mode, our_mode, their_mode);
if (ret) {
rollback_lock_file(&lock);
return !!ret;
}
return write_locked_index(&the_index, &lock, COMMIT_LOCK);
}
......@@ -8,18 +8,6 @@
static const char builtin_merge_recursive_usage[] =
"git %s <base>... -- <head> <remote> ...";
static char *better_branch_name(const char *branch)
{
static char githead_env[8 + GIT_MAX_HEXSZ + 1];
char *name;
if (strlen(branch) != the_hash_algo->hexsz)
return xstrdup(branch);
xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
name = getenv(githead_env);
return xstrdup(name ? name : branch);
}
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
{
const struct object_id *bases[21];
......@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
if (get_oid(o.branch2, &h2))
die(_("could not resolve ref '%s'"), o.branch2);
o.branch1 = better1 = better_branch_name(o.branch1);
o.branch2 = better2 = better_branch_name(o.branch2);
o.branch1 = better1 = merge_get_better_branch_name(o.branch1);
o.branch2 = better2 = merge_get_better_branch_name(o.branch2);
if (o.verbosity >= 3)
printf(_("Merging %s with %s\n"), o.branch1, o.branch2);
......
/*
* Builtin "git merge-resolve"
*
* Copyright (c) 2020 Alban Gruin
*
* Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C
* Hamano.
*
* Resolve two trees, using enhanced multi-base read-tree.
*/
#include "cache.h"
#include "builtin.h"
#include "merge-strategies.h"
static const char builtin_merge_resolve_usage[] =
"git merge-resolve <bases>... -- <head> <remote>";
int cmd_merge_resolve(int argc, const char **argv, const char *prefix)
{
int i, is_baseless = 1, sep_seen = 0;
const char *head = NULL;
struct commit_list *bases = NULL, *remote = NULL;
struct commit_list **next_base = &bases;
if (argc < 5)
usage(builtin_merge_resolve_usage);
setup_work_tree();
if (repo_read_index(the_repository) < 0)
die("invalid index");
/* The first parameters up to -- are merge bases; the rest are
* heads. */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--") == 0)
sep_seen = 1;
else if (strcmp(argv[i], "-h") == 0)
usage(builtin_merge_resolve_usage);
else if (sep_seen && !head)
head = argv[i];
else if (remote) {
/* Give up if we are given two or more remotes.
* Not handling octopus. */
return 2;
} else {
struct object_id oid;
get_oid(argv[i], &oid);
is_baseless &= sep_seen;
if (!oideq(&oid, the_hash_algo->empty_tree)) {
struct commit *commit;
commit = lookup_commit_or_die(&oid, argv[i]);
if (sep_seen)
commit_list_append(commit, &remote);
else
next_base = commit_list_append(commit, next_base);
}
}
}
/* Give up if this is a baseless merge. */
if (is_baseless)
return 2;
return merge_strategies_resolve(the_repository, bases, head, remote);
}
......@@ -41,6 +41,7 @@
#include "commit-reach.h"
#include "wt-status.h"
#include "commit-graph.h"
#include "merge-strategies.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
......@@ -740,7 +741,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("unable to write %s"), get_index_file());
return clean ? 0 : 1;
} else {
} else if (!strcmp(strategy, "resolve"))
return merge_strategies_resolve(the_repository, common,
head_arg, remoteheads);
else if (!strcmp(strategy, "octopus"))
return merge_strategies_octopus(the_repository, common,
head_arg, remoteheads);
else {
return try_merge_command(the_repository,
strategy, xopts_nr, xopts,
common, head_arg, remoteheads);
......
......@@ -1904,7 +1904,7 @@ int checkout_fast_forward(struct repository *r,
const struct object_id *from,
const struct object_id *to,
int overwrite_ignore);
char *merge_get_better_branch_name(const char *branch);
int sane_execvp(const char *file, char *const argv[]);
......
#!/bin/sh
#
# Copyright (c) 2005 Junio C Hamano
#
# Resolve two or more trees.
#
. git-sh-setup
LF='
'
# The first parameters up to -- are merge bases; the rest are heads.
bases= head= remotes= sep_seen=
for arg
do
case ",$sep_seen,$head,$arg," in
*,--,)
sep_seen=yes
;;
,yes,,*)
head=$arg
;;
,yes,*)
remotes="$remotes$arg "
;;
*)
bases="$bases$arg "
;;
esac
done
# Reject if this is not an octopus -- resolve should be used instead.
case "$remotes" in
?*' '?*)
;;
*)
exit 2 ;;
esac
# MRC is the current "merge reference commit"
# MRT is the current "merge result tree"
if ! git diff-index --quiet --cached HEAD --
then
gettextln "Error: Your local changes to the following files would be overwritten by merge"
git diff-index --cached --name-only HEAD -- | sed -e 's/^/ /'
exit 2
fi
MRC=$(git rev-parse --verify -q $head)
MRT=$(git write-tree)
NON_FF_MERGE=0
OCTOPUS_FAILURE=0
for SHA1 in $remotes
do
case "$OCTOPUS_FAILURE" in
1)
# We allow only last one to have a hand-resolvable
# conflicts. Last round failed and we still had
# a head to merge.
gettextln "Automated merge did not work."
gettextln "Should not be doing an octopus."
exit 2
esac
eval pretty_name=\${GITHEAD_$SHA1:-$SHA1}
if test "$SHA1" = "$pretty_name"
then
SHA1_UP="$(echo "$SHA1" | tr a-z A-Z)"
eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name}
fi
common=$(git merge-base --all $SHA1 $MRC) ||
die "$(eval_gettext "Unable to find common commit with \$pretty_name")"
case "$LF$common$LF" in
*"$LF$SHA1$LF"*)
eval_gettextln "Already up to date with \$pretty_name"
continue
;;
esac
if test "$common,$NON_FF_MERGE" = "$MRC,0"
then
# The first head being merged was a fast-forward.
# Advance MRC to the head being merged, and use that
# tree as the intermediate result of the merge.
# We still need to count this as part of the parent set.
eval_gettextln "Fast-forwarding to: \$pretty_name"
git read-tree -u -m $head $SHA1 || exit
MRC=$SHA1 MRT=$(git write-tree)
continue
fi
NON_FF_MERGE=1
eval_gettextln "Trying simple merge with \$pretty_name"
git read-tree -u -m --aggressive $common $MRT $SHA1 || exit 2
next=$(git write-tree 2>/dev/null)
if test $? -ne 0
then
gettextln "Simple merge did not work, trying automatic merge."
git merge-index -o git-merge-one-file -a ||
OCTOPUS_FAILURE=1
next=$(git write-tree 2>/dev/null)
fi
MRC="$MRC $SHA1"
MRT=$next
done
exit "$OCTOPUS_FAILURE"
#!/bin/sh
#
# Copyright (c) Linus Torvalds, 2005
#
# This is the git per-file merge script, called with
#
# $1 - original file SHA1 (or empty)
# $2 - file in branch1 SHA1 (or empty)
# $3 - file in branch2 SHA1 (or empty)
# $4 - pathname in repository
# $5 - original file mode (or empty)
# $6 - file in branch1 mode (or empty)
# $7 - file in branch2 mode (or empty)
#
# Handle some trivial cases.. The _really_ trivial cases have
# been handled already by git read-tree, but that one doesn't
# do any merges that might change the tree layout.
USAGE='<orig blob> <our blob> <their blob> <path>'
USAGE="$USAGE <orig mode> <our mode> <their mode>"
LONG_USAGE="usage: git merge-one-file $USAGE
Blob ids and modes should be empty for missing files."
SUBDIRECTORY_OK=Yes
. git-sh-setup
cd_to_toplevel
require_work_tree
if test $# != 7
then
echo "$LONG_USAGE"
exit 1
fi
case "${1:-.}${2:-.}${3:-.}" in
#
# Deleted in both or deleted in one and unchanged in the other
#
"$1.." | "$1.$1" | "$1$1.")
if { test -z "$6" && test "$5" != "$7"; } ||
{ test -z "$7" && test "$5" != "$6"; }
then
echo "ERROR: File $4 deleted on one branch but had its" >&2
echo "ERROR: permissions changed on the other." >&2
exit 1
fi
if test -n "$2"
then
echo "Removing $4"
else
# read-tree checked that index matches HEAD already,
# so we know we do not have this path tracked.
# there may be an unrelated working tree file here,
# which we should just leave unmolested. Make sure
# we do not have it in the index, though.
exec git update-index --remove -- "$4"
fi
if test -f "$4"
then
rm -f -- "$4" &&
rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || :
fi &&
exec git update-index --remove -- "$4"
;;
#
# Added in one.
#
".$2.")
# the other side did not add and we added so there is nothing
# to be done, except making the path merged.
exec git update-index --add --cacheinfo "$6" "$2" "$4"
;;
"..$3")
echo "Adding $4"
if test -f "$4"
then
echo "ERROR: untracked $4 is overwritten by the merge." >&2
exit 1
fi
git update-index --add --cacheinfo "$7" "$3" "$4" &&
exec git checkout-index -u -f -- "$4"
;;
#
# Added in both, identically (check for same permissions).
#
".$3$2")
if test "$6" != "$7"
then
echo "ERROR: File $4 added identically in both branches," >&2
echo "ERROR: but permissions conflict $6->$7." >&2
exit 1
fi
echo "Adding $4"
git update-index --add --cacheinfo "$6" "$2" "$4" &&
exec git checkout-index -u -f -- "$4"
;;
#
# Modified in both, but differently.
#
"$1$2$3" | ".$2$3")
case ",$6,$7," in
*,120000,*)
echo "ERROR: $4: Not merging symbolic link changes." >&2
exit 1
;;
*,160000,*)
echo "ERROR: $4: Not merging conflicting submodule changes." >&2
exit 1
;;
esac
src1=$(git unpack-file $2)
src2=$(git unpack-file $3)
case "$1" in
'')
echo "Added $4 in both, but differently."
orig=$(git unpack-file $(git hash-object /dev/null))
;;
*)
echo "Auto-merging $4"
orig=$(git unpack-file $1)
;;
esac
git merge-file "$src1" "$orig" "$src2"
ret=$?
msg=
if test $ret != 0 || test -z "$1"
then
msg='content conflict'
ret=1
fi
# Create the working tree file, using "our tree" version from the
# index, and then store the result of the merge.
git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1
rm -f -- "$orig" "$src1" "$src2"
if test "$6" != "$7"
then
if test -n "$msg"
then
msg="$msg, "
fi
msg="${msg}permissions conflict: $5->$6,$7"
ret=1
fi
if test $ret != 0
then
echo "ERROR: $msg in $4" >&2
exit 1
fi
exec git update-index -- "$4"
;;
*)
echo "ERROR: $4: Not handling case $1 -> $2 -> $3" >&2
;;
esac
exit 1
#!/bin/sh
#
# Copyright (c) 2005 Linus Torvalds
# Copyright (c) 2005 Junio C Hamano
#
# Resolve two trees, using enhanced multi-base read-tree.
# The first parameters up to -- are merge bases; the rest are heads.
bases= head= remotes= sep_seen=
for arg
do
case ",$sep_seen,$head,$arg," in
*,--,)
sep_seen=yes
;;
,yes,,*)
head=$arg
;;
,yes,*)
remotes="$remotes$arg "
;;
*)
bases="$bases$arg "
;;
esac
done
# Give up if we are given two or more remotes -- not handling octopus.
case "$remotes" in
?*' '?*)
exit 2 ;;
esac
# Give up if this is a baseless merge.
if test '' = "$bases"
then
exit 2
fi
git update-index -q --refresh
git read-tree -u -m --aggressive $bases $head $remotes || exit 2
echo "Trying simple merge."
if result_tree=$(git write-tree 2>/dev/null)
then
exit 0
else
echo "Simple merge failed, trying Automatic merge."
if git merge-index -o git-merge-one-file -a
then
exit 0
else
exit 1
fi
fi
......@@ -548,10 +548,13 @@ static struct cmd_struct commands[] = {
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
{ "merge-index", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },
{ "merge-octopus", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },
{ "merge-ours", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },
{ "merge-one-file", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-resolve", cmd_merge_resolve, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
......
#include "cache.h"
#include "cache-tree.h"
#include "commit-reach.h"
#include "dir.h"
#include "entry.h"
#include "ll-merge.h"
#include "lockfile.h"
#include "merge-strategies.h"
#include "run-command.h"
#include "unpack-trees.h"
#include "xdiff-interface.h"
static int add_to_index_cacheinfo(struct index_state *istate,
unsigned int mode,
const struct object_id *oid, const char *path)
{
struct cache_entry *ce;
int len, option;
if (!verify_path(path, mode))
return error(_("Invalid path '%s'"), path);
len = strlen(path);
ce = make_empty_cache_entry(istate, len);
oidcpy(&ce->oid, oid);
memcpy(ce->name, path, len);
ce->ce_flags = create_ce_flags(0);
ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
if (assume_unchanged)
ce->ce_flags |= CE_VALID;
option = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
if (add_index_entry(istate, ce, option))
return error(_("%s: cannot add to the index"), path);
return 0;
}
static int checkout_from_index(struct index_state *istate, const char *path)
{
struct checkout state = CHECKOUT_INIT;
struct cache_entry *ce;
state.istate = istate;
state.force = 1;
state.base_dir = "";
state.base_dir_len = 0;
ce = index_file_exists(istate, path, strlen(path), 0);
if (checkout_entry(ce, &state, NULL, NULL) < 0)
return error(_("%s: cannot checkout file"), path);
return 0;
}
static int merge_one_file_deleted(struct index_state *istate,
const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)
{
if ((our_blob && orig_mode != our_mode) ||
(their_blob && orig_mode != their_mode))
return error(_("File %s deleted on one branch but had its "
"permissions changed on the other."), path);
if (our_blob) {
printf(_("Removing %s\n"), path);
if (file_exists(path))
remove_path(path);
}
if (remove_file_from_index(istate, path))
return error("%s: cannot remove from the index", path);
return 0;
}
static int do_merge_one_file(struct index_state *istate,
const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)
{
int ret, i, dest;
ssize_t written;
mmbuffer_t result = {NULL, 0};
mmfile_t mmfs[3];
struct ll_merge_options merge_opts = {0};
struct cache_entry *ce;
if (our_mode == S_IFLNK || their_mode == S_IFLNK)
return error(_("%s: Not merging symbolic link changes."), path);
else if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)
return error(_("%s: Not merging conflicting submodule changes."), path);
read_mmblob(mmfs + 1, our_blob);
read_mmblob(mmfs + 2, their_blob);
if (orig_blob) {
printf(_("Auto-merging %s\n"), path);
read_mmblob(mmfs + 0, orig_blob);
} else {
printf(_("Added %s in both, but differently.\n"), path);
read_mmblob(mmfs + 0, &null_oid);
}
merge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;
ret = ll_merge(&result, path,
mmfs + 0, "orig",
mmfs + 1, "our",
mmfs + 2, "their",
istate, &merge_opts);
for (i = 0; i < 3; i++)
free(mmfs[i].ptr);
if (ret < 0) {
free(result.ptr);
return error(_("Failed to execute internal merge"));
}
/*
* Create the working tree file, using "our tree" version from
* the index, and then store the result of the merge.
*/
ce = index_file_exists(istate, path, strlen(path), 0);
if (!ce)
BUG("file is not present in the cache?");
unlink(path);
if ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {
free(result.ptr);
return error_errno(_("failed to open file '%s'"), path);
}
written = write_in_full(dest, result.ptr, result.size);
close(dest);
free(result.ptr);
if (written < 0)
return error_errno(_("failed to write to '%s'"), path);
if (ret != 0 || !orig_blob)
ret = error(_("content conflict in %s"), path);
if (our_mode != their_mode)
return error(_("permission conflict: %o->%o,%o in %s"),
orig_mode, our_mode, their_mode, path);
if (ret)
return -1;
return add_file_to_index(istate, path, 0);
}
int merge_strategies_one_file(struct repository *r,
const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode,
unsigned int their_mode)
{
if (orig_blob &&
((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||
(!our_blob && their_blob && oideq(orig_blob, their_blob))))
/* Deleted in both or deleted in one and unchanged in the other. */
return merge_one_file_deleted(r->index,
orig_blob, our_blob, their_blob, path,
orig_mode, our_mode, their_mode);
else if (!orig_blob && our_blob && !their_blob) {
/*
* Added in one. The other side did not add and we
* added so there is nothing to be done, except making
* the path merged.
*/
return add_to_index_cacheinfo(r->index, our_mode, our_blob, path);
} else if (!orig_blob && !our_blob && their_blob) {
printf(_("Adding %s\n"), path);
if (file_exists(path))
return error(_("untracked %s is overwritten by the merge."), path);
if (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))
return -1;
return checkout_from_index(r->index, path);
} else if (!orig_blob && our_blob && their_blob &&
oideq(our_blob, their_blob)) {
/* Added in both, identically (check for same permissions). */
if (our_mode != their_mode)
return error(_("File %s added identically in both branches, "
"but permissions conflict %o->%o."),
path, our_mode, their_mode);
printf(_("Adding %s\n"), path);
if (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))
return -1;
return checkout_from_index(r->index, path);
} else if (our_blob && their_blob)
/* Modified in both, but differently. */
return do_merge_one_file(r->index,
orig_blob, our_blob, their_blob, path,
orig_mode, our_mode, their_mode);
else {
char orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},
their_hex[GIT_MAX_HEXSZ] = {0};
if (orig_blob)
oid_to_hex_r(orig_hex, orig_blob);
if (our_blob)
oid_to_hex_r(our_hex, our_blob);
if (their_blob)
oid_to_hex_r(their_hex, their_blob);
return error(_("%s: Not handling case %s -> %s -> %s"),
path, orig_hex, our_hex, their_hex);
}
return 0;
}
int merge_one_file_cb(const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,
void *data)
{
return merge_strategies_one_file((struct repository *)data,
orig_blob, our_blob, their_blob, path,
orig_mode, our_mode, their_mode);
}
int merge_program_cb(const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,
void *data)
{
char ownbuf[3][GIT_MAX_HEXSZ] = {{0}};
const char *arguments[] = { (char *)data, "", "", "", path,
ownbuf[0], ownbuf[1], ownbuf[2],
NULL };
if (orig_blob)
arguments[1] = oid_to_hex(orig_blob);
if (our_blob)
arguments[2] = oid_to_hex(our_blob);
if (their_blob)
arguments[3] = oid_to_hex(their_blob);
xsnprintf(ownbuf[0], sizeof(ownbuf[0]), "%o", orig_mode);
xsnprintf(ownbuf[1], sizeof(ownbuf[1]), "%o", our_mode);
xsnprintf(ownbuf[2], sizeof(ownbuf[2]), "%o", their_mode);
return run_command_v_opt(arguments, 0);
}
static int merge_entry(struct index_state *istate, int quiet, int pos,
const char *path, merge_cb cb, void *data)
{
int found = 0;
const struct object_id *oids[3] = {NULL};
unsigned int modes[3] = {0};
do {
const struct cache_entry *ce = istate->cache[pos];
int stage = ce_stage(ce);
if (strcmp(ce->name, path))
break;
found++;
oids[stage - 1] = &ce->oid;
modes[stage - 1] = ce->ce_mode;
} while (++pos < istate->cache_nr);
if (!found)
return error(_("%s is not in the cache"), path);
if (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {
if (!quiet)
error(_("Merge program failed"));
return -2;
}
return found;
}
int merge_one_path(struct index_state *istate, int oneshot, int quiet,
const char *path, merge_cb cb, void *data)
{
int pos = index_name_pos(istate, path, strlen(path)), ret;
/*
* If it already exists in the cache as stage0, it's
* already merged and there is nothing to do.
*/
if (pos < 0) {
ret = merge_entry(istate, quiet, -pos - 1, path, cb, data);
if (ret == -1)
return -1;
else if (ret == -2)
return 1;
}
return 0;
}
int merge_all(struct index_state *istate, int oneshot, int quiet,
merge_cb cb, void *data)
{
int err = 0, i, ret;
for (i = 0; i < istate->cache_nr; i++) {
const struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
continue;
ret = merge_entry(istate, quiet, i, ce->name, cb, data);
if (ret > 0)
i += ret - 1;
else if (ret == -1)
return -1;
else if (ret == -2) {
if (oneshot)
err++;
else
return 1;
}
}
return err;
}
static int add_tree(const struct object_id *oid, struct tree_desc *t)
{
struct tree *tree;
tree = parse_tree_indirect(oid);
if (parse_tree(tree))
return -1;
init_tree_desc(t, tree->buffer, tree->size);
return 0;
}
int merge_strategies_resolve(struct repository *r,
struct commit_list *bases, const char *head_arg,
struct commit_list *remote)
{
int i = 0;
struct lock_file lock = LOCK_INIT;
struct tree_desc t[MAX_UNPACK_TREES];
struct unpack_trees_options opts;
struct object_id head, oid;
struct commit_list *j;
if (head_arg)
get_oid(head_arg, &head);
repo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);
refresh_index(r->index, 0, NULL, NULL, NULL);
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.src_index = r->index;
opts.dst_index = r->index;
opts.update = 1;
opts.merge = 1;
opts.aggressive = 1;
for (j = bases; j && j->item; j = j->next) {
if (add_tree(&j->item->object.oid, t + (i++)))
goto out;
}
if (head_arg && add_tree(&head, t + (i++)))
goto out;
if (remote && add_tree(&remote->item->object.oid, t + (i++)))
goto out;
if (i == 1)
opts.fn = oneway_merge;
else if (i == 2) {
opts.fn = twoway_merge;
opts.initial_checkout = is_index_unborn(r->index);
} else if (i >= 3) {
opts.fn = threeway_merge;
opts.head_idx = i - 1;
}
if (unpack_trees(i, t, &opts))
goto out;
puts(_("Trying simple merge."));
write_locked_index(r->index, &lock, COMMIT_LOCK);
if (write_index_as_tree(&oid, r->index, r->index_file,
WRITE_TREE_SILENT, NULL)) {
int ret;
puts(_("Simple merge failed, trying Automatic merge."));
repo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);
ret = merge_all(r->index, 0, 0, merge_one_file_cb, r);
write_locked_index(r->index, &lock, COMMIT_LOCK);
return !!ret;
}
return 0;
out:
rollback_lock_file(&lock);
return 2;
}
static int fast_forward(struct repository *r, const struct object_id *oids,
int nr, int aggressive)
{
int i;
struct tree_desc t[MAX_UNPACK_TREES];
struct unpack_trees_options opts;
struct lock_file lock = LOCK_INIT;
repo_read_index_preload(r, NULL, 0);
if (refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL))
return -1;
repo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.src_index = r->index;
opts.dst_index = r->index;
opts.merge = 1;
opts.update = 1;
opts.aggressive = aggressive;
for (i = 0; i < nr; i++) {
struct tree *tree;
tree = parse_tree_indirect(oids + i);
if (parse_tree(tree))
return -1;
init_tree_desc(t + i, tree->buffer, tree->size);
}
if (nr == 1)
opts.fn = oneway_merge;
else if (nr == 2) {
opts.fn = twoway_merge;
opts.initial_checkout = is_index_unborn(r->index);
} else if (nr >= 3) {
opts.fn = threeway_merge;
opts.head_idx = nr - 1;
}
if (unpack_trees(nr, t, &opts))
return -1;
if (write_locked_index(r->index, &lock, COMMIT_LOCK))
return error(_("unable to write new index file"));
return 0;
}
static int write_tree(struct repository *r, struct tree **reference_tree)
{
struct object_id oid;
int ret;
ret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL);
if (!ret)
*reference_tree = lookup_tree(r, &oid);
return ret;
}
int merge_strategies_octopus(struct repository *r,
struct commit_list *bases, const char *head_arg,
struct commit_list *remotes)
{
int non_ff_merge = 0, ret = 0, references = 1;
struct commit **reference_commit;
struct tree *reference_tree;
struct commit_list *j;
struct object_id head;
struct strbuf sb = STRBUF_INIT;
get_oid(head_arg, &head);
reference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));
reference_commit[0] = lookup_commit_reference(r, &head);
reference_tree = repo_get_commit_tree(r, reference_commit[0]);
if (repo_index_has_changes(r, reference_tree, &sb)) {
error(_("Your local changes to the following files "
"would be overwritten by merge:\n %s"),
sb.buf);
strbuf_release(&sb);
ret = 2;
goto out;
}
for (j = remotes; j && j->item; j = j->next) {
struct commit *c = j->item;
struct object_id *oid = &c->object.oid;
struct commit_list *common, *k;
char *branch_name;
int can_ff = 1;
if (ret) {
/*
* We allow only last one to have a
* hand-resolvable conflicts. Last round failed
* and we still had a head to merge.
*/
puts(_("Automated merge did not work."));
puts(_("Should not be doing an octopus."));
ret = 2;
goto out;
}
branch_name = merge_get_better_branch_name(oid_to_hex(oid));
common = get_merge_bases_many(c, references, reference_commit);
if (!common)
die(_("Unable to find common commit with %s"), branch_name);
for (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);
if (k) {
printf(_("Already up to date with %s\n"), branch_name);
free(branch_name);
free_commit_list(common);
continue;
}
if (!non_ff_merge) {
int i;
for (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {
can_ff = oideq(&k->item->object.oid,
&reference_commit[i]->object.oid);
}
}
if (!non_ff_merge && can_ff) {
/*
* The first head being merged was a
* fast-forward. Advance the reference commit
* to the head being merged, and use that tree
* as the intermediate result of the merge. We
* still need to count this as part of the
* parent set.
*/
struct object_id oids[2];
printf(_("Fast-forwarding to: %s\n"), branch_name);
oidcpy(oids, &head);
oidcpy(oids + 1, oid);
ret = fast_forward(r, oids, 2, 0);
if (ret) {
free(branch_name);
free_commit_list(common);
goto out;
}
references = 0;
write_tree(r, &reference_tree);
} else {
int i = 0;
struct tree *next = NULL;
struct object_id oids[MAX_UNPACK_TREES];
non_ff_merge = 1;
printf(_("Trying simple merge with %s\n"), branch_name);
for (k = common; k; k = k->next)
oidcpy(oids + (i++), &k->item->object.oid);
oidcpy(oids + (i++), &reference_tree->object.oid);
oidcpy(oids + (i++), oid);
if (fast_forward(r, oids, i, 1)) {
ret = 2;
free(branch_name);
free_commit_list(common);
goto out;
}
if (write_tree(r, &next)) {
struct lock_file lock = LOCK_INIT;
puts(_("Simple merge did not work, trying automatic merge."));
repo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);
ret = !!merge_all(r->index, 0, 0, merge_one_file_cb, r);
write_locked_index(r->index, &lock, COMMIT_LOCK);
write_tree(r, &next);
}
reference_tree = next;
}
reference_commit[references++] = c;
free(branch_name);
free_commit_list(common);
}
out:
free(reference_commit);
return ret;
}
#ifndef MERGE_STRATEGIES_H
#define MERGE_STRATEGIES_H
#include "commit.h"
#include "object.h"
int merge_strategies_one_file(struct repository *r,
const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode,
unsigned int their_mode);
typedef int (*merge_cb)(const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,
void *data);
int merge_one_file_cb(const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,
void *data);
int merge_program_cb(const struct object_id *orig_blob,
const struct object_id *our_blob,
const struct object_id *their_blob, const char *path,
unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,
void *data);
int merge_one_path(struct index_state *istate, int oneshot, int quiet,
const char *path, merge_cb cb, void *data);
int merge_all(struct index_state *istate, int oneshot, int quiet,
merge_cb cb, void *data);
int merge_strategies_resolve(struct repository *r,
struct commit_list *bases, const char *head_arg,
struct commit_list *remote);
int merge_strategies_octopus(struct repository *r,
struct commit_list *bases, const char *head_arg,
struct commit_list *remote);
#endif /* MERGE_STRATEGIES_H */
......@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,
return error(_("unable to write new index file"));
return 0;
}
char *merge_get_better_branch_name(const char *branch)
{
static char githead_env[8 + GIT_MAX_HEXSZ + 1];
char *name;
if (strlen(branch) != the_hash_algo->hexsz)
return xstrdup(branch);
xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
name = getenv(githead_env);
return xstrdup(name ? name : branch);
}
......@@ -33,6 +33,7 @@
#include "commit-reach.h"
#include "rebase-interactive.h"
#include "reset.h"
#include "merge-strategies.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
......@@ -2008,9 +2009,18 @@ static int do_pick_commit(struct repository *r,
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
res |= try_merge_command(r, opts->strategy,
opts->xopts_nr, (const char **)opts->xopts,
common, oid_to_hex(&head), remotes);
if (!strcmp(opts->strategy, "resolve")) {
repo_read_index(r);
res |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);
} else if (!strcmp(opts->strategy, "octopus")) {
repo_read_index(r);
res |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);
} else
res |= try_merge_command(r, opts->strategy,
opts->xopts_nr, (const char **)opts->xopts,
common, oid_to_hex(&head), remotes);
free_commit_list(common);
free_commit_list(remotes);
}
......
......@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'
. ./test-lib.sh
test_expect_success setup '
cat "$TEST_DIRECTORY"/test-binary-1.png >m &&
git add m &&
git ls-files -s | sed -e "s/ 0 / 1 /" >E1 &&
......@@ -35,33 +34,19 @@ test_expect_success setup '
'
test_expect_success resolve '
rm -f a* m* &&
git reset --hard anchor &&
if git merge -s resolve master
then
echo Oops, should not have succeeded
false
else
git ls-files -s >current
test_cmp expect current
fi
test_must_fail git merge -s resolve master &&
git ls-files -s >current &&
test_cmp expect current
'
test_expect_success recursive '
rm -f a* m* &&
git reset --hard anchor &&
if git merge -s recursive master
then
echo Oops, should not have succeeded
false
else
git ls-files -s >current
test_cmp expect current
fi
test_must_fail git merge -s recursive master &&
git ls-files -s >current &&
test_cmp expect current
'
test_done
......@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '
test -h a/b
'
test_expect_failure 'do not lose untracked in merge (resolve)' '
test_expect_success 'do not lose untracked in merge (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
>a/b/c/e &&
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册