Merge branch 'db/checkout'

* db/checkout: (21 commits)
  checkout: error out when index is unmerged even with -m
  checkout: show progress when checkout takes long time while switching branches
  Add merge-subtree back
  checkout: updates to tracking report
  builtin-checkout.c: Remove unused prefix arguments in switch_branches path
  checkout: work from a subdirectory
  checkout: tone down the "forked status" diagnostic messages
  Clean up reporting differences on branch switch
  builtin-checkout.c: fix possible usage segfault
  checkout: notice when the switched branch is behind or forked
  Build in checkout
  Move code to clean up after a branch change to branch.c
  Library function to check for unmerged index entries
  Use diff -u instead of diff in t7201
  Move create_branch into a library file
  Build-in merge-recursive
  Add "skip_unmerged" option to unpack_trees.
  Discard "deleted" cache entries after using them to update the working tree
  Send unpack-trees debugging output to stderr
  Add flag to make unpack_trees() not print errors.
  ...

Conflicts:

	Makefile
This commit is contained in:
Junio C Hamano 2008-02-27 12:53:26 -08:00
commit 5a4d707a6d
20 changed files with 1002 additions and 271 deletions

View File

@ -226,7 +226,7 @@ BASIC_CFLAGS =
BASIC_LDFLAGS =
SCRIPT_SH = \
git-bisect.sh git-checkout.sh \
git-bisect.sh \
git-clone.sh \
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
@ -265,23 +265,23 @@ PROGRAMS = \
git-upload-pack$X \
git-pack-redundant$X git-var$X \
git-merge-tree$X git-imap-send$X \
git-merge-recursive$X \
$(EXTRA_PROGRAMS)
# Empty...
EXTRA_PROGRAMS =
# List built-in command $C whose implementation cmd_$C() is not in
# builtin-$C.o but is linked in as part of some other command.
BUILT_INS = \
git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
git-get-tar-commit-id$X git-init$X git-repo-config$X \
git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \
git-merge-subtree$X \
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
ALL_PROGRAMS += git-merge-subtree$X
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
@ -327,7 +327,7 @@ LIB_OBJS = \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
transport.o bundle.o walker.o parse-options.o ws.o archive.o \
transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \
alias.o
BUILTIN_OBJS = \
@ -340,6 +340,7 @@ BUILTIN_OBJS = \
builtin-bundle.o \
builtin-cat-file.o \
builtin-check-attr.o \
builtin-checkout.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
builtin-clean.o \
@ -370,6 +371,7 @@ BUILTIN_OBJS = \
builtin-merge-base.o \
builtin-merge-file.o \
builtin-merge-ours.o \
builtin-merge-recursive.o \
builtin-mv.o \
builtin-name-rev.o \
builtin-pack-objects.o \
@ -840,9 +842,6 @@ help.o: help.c common-cmds.h GIT-CFLAGS
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_SQ)"' $<
git-merge-subtree$X: git-merge-recursive$X
$(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@

148
branch.c Normal file
View File

@ -0,0 +1,148 @@
#include "cache.h"
#include "branch.h"
#include "refs.h"
#include "remote.h"
#include "commit.h"
struct tracking {
struct refspec spec;
char *src;
const char *remote;
int matches;
};
static int find_tracked_branch(struct remote *remote, void *priv)
{
struct tracking *tracking = priv;
if (!remote_find_tracking(remote, &tracking->spec)) {
if (++tracking->matches == 1) {
tracking->src = tracking->spec.src;
tracking->remote = remote->name;
} else {
free(tracking->spec.src);
if (tracking->src) {
free(tracking->src);
tracking->src = NULL;
}
}
tracking->spec.src = NULL;
}
return 0;
}
/*
* This is called when new_ref is branched off of orig_ref, and tries
* to infer the settings for branch.<new_ref>.{remote,merge} from the
* config.
*/
static int setup_tracking(const char *new_ref, const char *orig_ref)
{
char key[1024];
struct tracking tracking;
if (strlen(new_ref) > 1024 - 7 - 7 - 1)
return error("Tracking not set up: name too long: %s",
new_ref);
memset(&tracking, 0, sizeof(tracking));
tracking.spec.dst = (char *)orig_ref;
if (for_each_remote(find_tracked_branch, &tracking) ||
!tracking.matches)
return 1;
if (tracking.matches > 1)
return error("Not tracking: ambiguous information for ref %s",
orig_ref);
if (tracking.matches == 1) {
sprintf(key, "branch.%s.remote", new_ref);
git_config_set(key, tracking.remote ? tracking.remote : ".");
sprintf(key, "branch.%s.merge", new_ref);
git_config_set(key, tracking.src);
free(tracking.src);
printf("Branch %s set up to track remote branch %s.\n",
new_ref, orig_ref);
}
return 0;
}
void create_branch(const char *head,
const char *name, const char *start_name,
int force, int reflog, int track)
{
struct ref_lock *lock;
struct commit *commit;
unsigned char sha1[20];
char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
int forcing = 0;
snprintf(ref, sizeof ref, "refs/heads/%s", name);
if (check_ref_format(ref))
die("'%s' is not a valid branch name.", name);
if (resolve_ref(ref, sha1, 1, NULL)) {
if (!force)
die("A branch named '%s' already exists.", name);
else if (!is_bare_repository() && !strcmp(head, name))
die("Cannot force update the current branch.");
forcing = 1;
}
real_ref = NULL;
if (get_sha1(start_name, sha1))
die("Not a valid object name: '%s'.", start_name);
switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
case 0:
/* Not branching from any existing branch */
real_ref = NULL;
break;
case 1:
/* Unique completion -- good */
break;
default:
die("Ambiguous object name: '%s'.", start_name);
break;
}
if ((commit = lookup_commit_reference(sha1)) == NULL)
die("Not a valid branch point: '%s'.", start_name);
hashcpy(sha1, commit->object.sha1);
lock = lock_any_ref_for_update(ref, NULL, 0);
if (!lock)
die("Failed to lock ref for update: %s.", strerror(errno));
if (reflog)
log_all_ref_updates = 1;
if (forcing)
snprintf(msg, sizeof msg, "branch: Reset from %s",
start_name);
else
snprintf(msg, sizeof msg, "branch: Created from %s",
start_name);
/* When branching off a remote branch, set up so that git-pull
automatically merges from there. So far, this is only done for
remotes registered via .git/config. */
if (real_ref && track)
setup_tracking(name, real_ref);
if (write_ref_sha1(lock, sha1, msg) < 0)
die("Failed to write ref: %s.", strerror(errno));
if (real_ref)
free(real_ref);
}
void remove_branch_state(void)
{
unlink(git_path("MERGE_HEAD"));
unlink(git_path("rr-cache/MERGE_RR"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("SQUASH_MSG"));
}

24
branch.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef BRANCH_H
#define BRANCH_H
/* Functions for acting on the information about branches. */
/*
* Creates a new branch, where head is the branch currently checked
* out, name is the new branch name, start_name is the name of the
* existing branch that the new branch should start from, force
* enables overwriting an existing (non-head) branch, reflog creates a
* reflog for the branch, and track causes the new branch to be
* configured to merge the remote branch that start_name is a tracking
* branch for (if any).
*/
void create_branch(const char *head, const char *name, const char *start_name,
int force, int reflog, int track);
/*
* Remove information about the state of working on the current
* branch. (E.g., MERGE_HEAD)
*/
void remove_branch_state(void);
#endif

View File

@ -12,6 +12,7 @@
#include "builtin.h"
#include "remote.h"
#include "parse-options.h"
#include "branch.h"
static const char * const builtin_branch_usage[] = {
"git-branch [options] [-r | -a]",
@ -359,141 +360,6 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
free_ref_list(&ref_list);
}
struct tracking {
struct refspec spec;
char *src;
const char *remote;
int matches;
};
static int find_tracked_branch(struct remote *remote, void *priv)
{
struct tracking *tracking = priv;
if (!remote_find_tracking(remote, &tracking->spec)) {
if (++tracking->matches == 1) {
tracking->src = tracking->spec.src;
tracking->remote = remote->name;
} else {
free(tracking->spec.src);
if (tracking->src) {
free(tracking->src);
tracking->src = NULL;
}
}
tracking->spec.src = NULL;
}
return 0;
}
/*
* This is called when new_ref is branched off of orig_ref, and tries
* to infer the settings for branch.<new_ref>.{remote,merge} from the
* config.
*/
static int setup_tracking(const char *new_ref, const char *orig_ref)
{
char key[1024];
struct tracking tracking;
if (strlen(new_ref) > 1024 - 7 - 7 - 1)
return error("Tracking not set up: name too long: %s",
new_ref);
memset(&tracking, 0, sizeof(tracking));
tracking.spec.dst = (char *)orig_ref;
if (for_each_remote(find_tracked_branch, &tracking) ||
!tracking.matches)
return 1;
if (tracking.matches > 1)
return error("Not tracking: ambiguous information for ref %s",
orig_ref);
if (tracking.matches == 1) {
sprintf(key, "branch.%s.remote", new_ref);
git_config_set(key, tracking.remote ? tracking.remote : ".");
sprintf(key, "branch.%s.merge", new_ref);
git_config_set(key, tracking.src);
free(tracking.src);
printf("Branch %s set up to track remote branch %s.\n",
new_ref, orig_ref);
}
return 0;
}
static void create_branch(const char *name, const char *start_name,
int force, int reflog, int track)
{
struct ref_lock *lock;
struct commit *commit;
unsigned char sha1[20];
char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
int forcing = 0;
snprintf(ref, sizeof ref, "refs/heads/%s", name);
if (check_ref_format(ref))
die("'%s' is not a valid branch name.", name);
if (resolve_ref(ref, sha1, 1, NULL)) {
if (!force)
die("A branch named '%s' already exists.", name);
else if (!is_bare_repository() && !strcmp(head, name))
die("Cannot force update the current branch.");
forcing = 1;
}
real_ref = NULL;
if (get_sha1(start_name, sha1))
die("Not a valid object name: '%s'.", start_name);
switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
case 0:
/* Not branching from any existing branch */
real_ref = NULL;
break;
case 1:
/* Unique completion -- good */
break;
default:
die("Ambiguous object name: '%s'.", start_name);
break;
}
if ((commit = lookup_commit_reference(sha1)) == NULL)
die("Not a valid branch point: '%s'.", start_name);
hashcpy(sha1, commit->object.sha1);
lock = lock_any_ref_for_update(ref, NULL, 0);
if (!lock)
die("Failed to lock ref for update: %s.", strerror(errno));
if (reflog)
log_all_ref_updates = 1;
if (forcing)
snprintf(msg, sizeof msg, "branch: Reset from %s",
start_name);
else
snprintf(msg, sizeof msg, "branch: Created from %s",
start_name);
/* When branching off a remote branch, set up so that git-pull
automatically merges from there. So far, this is only done for
remotes registered via .git/config. */
if (real_ref && track)
setup_tracking(name, real_ref);
if (write_ref_sha1(lock, sha1, msg) < 0)
die("Failed to write ref: %s.", strerror(errno));
if (real_ref)
free(real_ref);
}
static void rename_branch(const char *oldname, const char *newname, int force)
{
char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
@ -618,7 +484,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
else if (rename && (argc == 2))
rename_branch(argv[0], argv[1], rename > 1);
else if (argc <= 2)
create_branch(argv[0], (argc == 2) ? argv[1] : head,
create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
force_create, reflog, track);
else
usage_with_options(builtin_branch_usage, options);

573
builtin-checkout.c Normal file
View File

@ -0,0 +1,573 @@
#include "cache.h"
#include "builtin.h"
#include "parse-options.h"
#include "refs.h"
#include "commit.h"
#include "tree.h"
#include "tree-walk.h"
#include "unpack-trees.h"
#include "dir.h"
#include "run-command.h"
#include "merge-recursive.h"
#include "branch.h"
#include "diff.h"
#include "revision.h"
#include "remote.h"
static const char * const checkout_usage[] = {
"git checkout [options] <branch>",
"git checkout [options] [<branch>] -- <file>...",
NULL,
};
static int post_checkout_hook(struct commit *old, struct commit *new,
int changed)
{
struct child_process proc;
const char *name = git_path("hooks/post-checkout");
const char *argv[5];
if (access(name, X_OK) < 0)
return 0;
memset(&proc, 0, sizeof(proc));
argv[0] = name;
argv[1] = xstrdup(sha1_to_hex(old->object.sha1));
argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
argv[3] = changed ? "1" : "0";
argv[4] = NULL;
proc.argv = argv;
proc.no_stdin = 1;
proc.stdout_to_stderr = 1;
return run_command(&proc);
}
static int update_some(const unsigned char *sha1, const char *base, int baselen,
const char *pathname, unsigned mode, int stage)
{
int len;
struct cache_entry *ce;
if (S_ISGITLINK(mode))
return 0;
if (S_ISDIR(mode))
return READ_TREE_RECURSIVE;
len = baselen + strlen(pathname);
ce = xcalloc(1, cache_entry_size(len));
hashcpy(ce->sha1, sha1);
memcpy(ce->name, base, baselen);
memcpy(ce->name + baselen, pathname, len - baselen);
ce->ce_flags = create_ce_flags(len, 0);
ce->ce_mode = create_ce_mode(mode);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
return 0;
}
static int read_tree_some(struct tree *tree, const char **pathspec)
{
int newfd;
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
newfd = hold_locked_index(lock_file, 1);
read_cache();
read_tree_recursive(tree, "", 0, 0, pathspec, update_some);
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock_file))
die("unable to write new index file");
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
* with any non-zero return code.
*/
return 0;
}
static int checkout_paths(const char **pathspec)
{
int pos;
struct checkout state;
static char *ps_matched;
unsigned char rev[20];
int flag;
struct commit *head;
for (pos = 0; pathspec[pos]; pos++)
;
ps_matched = xcalloc(1, pos);
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
pathspec_match(pathspec, ps_matched, ce->name, 0);
}
if (report_path_error(ps_matched, pathspec, 0))
return 1;
memset(&state, 0, sizeof(state));
state.force = 1;
state.refresh_cache = 1;
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
if (pathspec_match(pathspec, NULL, ce->name, 0)) {
checkout_entry(ce, &state, NULL);
}
}
resolve_ref("HEAD", rev, 0, &flag);
head = lookup_commit_reference_gently(rev, 1);
return post_checkout_hook(head, head, 0);
}
static void show_local_changes(struct object *head)
{
struct rev_info rev;
/* I think we want full paths, even if we're in a subdirectory. */
init_revisions(&rev, NULL);
rev.abbrev = 0;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
}
static void describe_detached_head(char *msg, struct commit *commit)
{
struct strbuf sb;
strbuf_init(&sb, 0);
parse_commit(commit);
pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0);
fprintf(stderr, "%s %s... %s\n", msg,
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
strbuf_release(&sb);
}
static int reset_to_new(struct tree *tree, int quiet)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
opts.update = 1;
opts.reset = 1;
opts.merge = 1;
opts.fn = oneway_merge;
opts.verbose_update = !quiet;
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
if (unpack_trees(1, &tree_desc, &opts))
return 128;
return 0;
}
static void reset_clean_to_new(struct tree *tree, int quiet)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
opts.skip_unmerged = 1;
opts.reset = 1;
opts.merge = 1;
opts.fn = oneway_merge;
opts.verbose_update = !quiet;
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
if (unpack_trees(1, &tree_desc, &opts))
exit(128);
}
struct checkout_opts {
int quiet;
int merge;
int force;
char *new_branch;
int new_branch_log;
int track;
};
struct branch_info {
const char *name; /* The short name used */
const char *path; /* The full name of a real branch */
struct commit *commit; /* The named commit */
};
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf;
strbuf_init(&buf, 0);
strbuf_addstr(&buf, "refs/heads/");
strbuf_addstr(&buf, branch->name);
branch->path = strbuf_detach(&buf, NULL);
}
static int merge_working_tree(struct checkout_opts *opts,
struct branch_info *old, struct branch_info *new)
{
int ret;
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
int newfd = hold_locked_index(lock_file, 1);
read_cache();
if (opts->force) {
ret = reset_to_new(new->commit->tree, opts->quiet);
if (ret)
return ret;
} else {
struct tree_desc trees[2];
struct tree *tree;
struct unpack_trees_options topts;
memset(&topts, 0, sizeof(topts));
topts.head_idx = -1;
refresh_cache(REFRESH_QUIET);
if (unmerged_cache()) {
error("you need to resolve your current index first");
return 1;
}
/* 2-way merge to the new branch */
topts.update = 1;
topts.merge = 1;
topts.gently = opts->merge;
topts.verbose_update = !opts->quiet;
topts.fn = twoway_merge;
topts.dir = xcalloc(1, sizeof(*topts.dir));
topts.dir->show_ignored = 1;
topts.dir->exclude_per_dir = ".gitignore";
tree = parse_tree_indirect(old->commit->object.sha1);
init_tree_desc(&trees[0], tree->buffer, tree->size);
tree = parse_tree_indirect(new->commit->object.sha1);
init_tree_desc(&trees[1], tree->buffer, tree->size);
if (unpack_trees(2, trees, &topts)) {
/*
* Unpack couldn't do a trivial merge; either
* give up or do a real merge, depending on
* whether the merge flag was used.
*/
struct tree *result;
struct tree *work;
if (!opts->merge)
return 1;
parse_commit(old->commit);
/* Do more real merge */
/*
* We update the index fully, then write the
* tree from the index, then merge the new
* branch with the current tree, with the old
* branch as the base. Then we reset the index
* (but not the working tree) to the new
* branch, leaving the working tree as the
* merged version, but skipping unmerged
* entries in the index.
*/
add_files_to_cache(0, NULL, NULL);
work = write_tree_from_memory();
ret = reset_to_new(new->commit->tree, opts->quiet);
if (ret)
return ret;
merge_trees(new->commit->tree, work, old->commit->tree,
new->name, "local", &result);
reset_clean_to_new(new->commit->tree, opts->quiet);
}
}
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock_file))
die("unable to write new index file");
if (!opts->force)
show_local_changes(&new->commit->object);
return 0;
}
static void report_tracking(struct branch_info *new, struct checkout_opts *opts)
{
/*
* We have switched to a new branch; is it building on
* top of another branch, and if so does that other branch
* have changes we do not have yet?
*/
char *base;
unsigned char sha1[20];
struct commit *ours, *theirs;
char symmetric[84];
struct rev_info revs;
const char *rev_argv[10];
int rev_argc;
int num_ours, num_theirs;
const char *remote_msg;
struct branch *branch = branch_get(new->name);
/*
* Nothing to report unless we are marked to build on top of
* somebody else.
*/
if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
return;
/*
* If what we used to build on no longer exists, there is
* nothing to report.
*/
base = branch->merge[0]->dst;
if (!resolve_ref(base, sha1, 1, NULL))
return;
theirs = lookup_commit(sha1);
ours = new->commit;
if (!hashcmp(sha1, ours->object.sha1))
return; /* we are the same */
/* Run "rev-list --left-right ours...theirs" internally... */
rev_argc = 0;
rev_argv[rev_argc++] = NULL;
rev_argv[rev_argc++] = "--left-right";
rev_argv[rev_argc++] = symmetric;
rev_argv[rev_argc++] = "--";
rev_argv[rev_argc] = NULL;
strcpy(symmetric, sha1_to_hex(ours->object.sha1));
strcpy(symmetric + 40, "...");
strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
init_revisions(&revs, NULL);
setup_revisions(rev_argc, rev_argv, &revs, NULL);
prepare_revision_walk(&revs);
/* ... and count the commits on each side. */
num_ours = 0;
num_theirs = 0;
while (1) {
struct commit *c = get_revision(&revs);
if (!c)
break;
if (c->object.flags & SYMMETRIC_LEFT)
num_ours++;
else
num_theirs++;
}
if (!prefixcmp(base, "refs/remotes/")) {
remote_msg = " remote";
base += strlen("refs/remotes/");
} else {
remote_msg = "";
}
if (!num_theirs)
printf("Your branch is ahead of the tracked%s branch '%s' "
"by %d commit%s.\n",
remote_msg, base,
num_ours, (num_ours == 1) ? "" : "s");
else if (!num_ours)
printf("Your branch is behind the tracked%s branch '%s' "
"by %d commit%s,\n"
"and can be fast-forwarded.\n",
remote_msg, base,
num_theirs, (num_theirs == 1) ? "" : "s");
else
printf("Your branch and the tracked%s branch '%s' "
"have diverged,\nand respectively "
"have %d and %d different commit(s) each.\n",
remote_msg, base,
num_ours, num_theirs);
}
static void update_refs_for_switch(struct checkout_opts *opts,
struct branch_info *old,
struct branch_info *new)
{
struct strbuf msg;
const char *old_desc;
if (opts->new_branch) {
create_branch(old->name, opts->new_branch, new->name, 0,
opts->new_branch_log, opts->track);
new->name = opts->new_branch;
setup_branch_path(new);
}
strbuf_init(&msg, 0);
old_desc = old->name;
if (!old_desc)
old_desc = sha1_to_hex(old->commit->object.sha1);
strbuf_addf(&msg, "checkout: moving from %s to %s",
old_desc, new->name);
if (new->path) {
create_symref("HEAD", new->path, msg.buf);
if (!opts->quiet) {
if (old->path && !strcmp(new->path, old->path))
fprintf(stderr, "Already on \"%s\"\n",
new->name);
else
fprintf(stderr, "Switched to%s branch \"%s\"\n",
opts->new_branch ? " a new" : "",
new->name);
}
} else if (strcmp(new->name, "HEAD")) {
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
REF_NODEREF, DIE_ON_ERR);
if (!opts->quiet) {
if (old->path)
fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b <new_branch_name>\n", new->name);
describe_detached_head("HEAD is now at", new->commit);
}
}
remove_branch_state();
strbuf_release(&msg);
if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
report_tracking(new, opts);
}
static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
{
int ret = 0;
struct branch_info old;
unsigned char rev[20];
int flag;
memset(&old, 0, sizeof(old));
old.path = resolve_ref("HEAD", rev, 0, &flag);
old.commit = lookup_commit_reference_gently(rev, 1);
if (!(flag & REF_ISSYMREF))
old.path = NULL;
if (old.path && !prefixcmp(old.path, "refs/heads/"))
old.name = old.path + strlen("refs/heads/");
if (!new->name) {
new->name = "HEAD";
new->commit = old.commit;
if (!new->commit)
die("You are on a branch yet to be born");
parse_commit(new->commit);
}
/*
* If the new thing isn't a branch and isn't HEAD and we're
* not starting a new branch, and we want messages, and we
* weren't on a branch, and we're moving to a new commit,
* describe the old commit.
*/
if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
!opts->quiet && !old.path && new->commit != old.commit)
describe_detached_head("Previous HEAD position was", old.commit);
if (!old.commit) {
if (!opts->quiet) {
fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n");
fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name);
}
opts->force = 1;
}
ret = merge_working_tree(opts, &old, new);
if (ret)
return ret;
update_refs_for_switch(opts, &old, new);
return post_checkout_hook(old.commit, new->commit, 1);
}
static int branch_track = 0;
static int git_checkout_config(const char *var, const char *value)
{
if (!strcmp(var, "branch.autosetupmerge"))
branch_track = git_config_bool(var, value);
return git_default_config(var, value);
}
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
struct checkout_opts opts;
unsigned char rev[20];
const char *arg;
struct branch_info new;
struct tree *source_tree = NULL;
struct option options[] = {
OPT__QUIET(&opts.quiet),
OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
OPT_BOOLEAN( 0 , "track", &opts.track, "track"),
OPT_BOOLEAN('f', NULL, &opts.force, "force"),
OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
OPT_END(),
};
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
git_config(git_checkout_config);
opts.track = branch_track;
argc = parse_options(argc, argv, options, checkout_usage, 0);
if (argc) {
arg = argv[0];
if (get_sha1(arg, rev))
;
else if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
new.name = arg;
setup_branch_path(&new);
if (resolve_ref(new.path, rev, 1, NULL))
new.commit = lookup_commit_reference(rev);
else
new.path = NULL;
parse_commit(new.commit);
source_tree = new.commit->tree;
argv++;
argc--;
} else if ((source_tree = parse_tree_indirect(rev))) {
argv++;
argc--;
}
}
if (argc && !strcmp(argv[0], "--")) {
argv++;
argc--;
}
if (!opts.new_branch && (opts.track != branch_track))
die("git checkout: --track and --no-track require -b");
if (opts.force && opts.merge)
die("git checkout: -f and -m are incompatible");
if (argc) {
const char **pathspec = get_pathspec(prefix, argv);
/* Checkout paths */
if (opts.new_branch || opts.force || opts.merge) {
if (argc == 1) {
die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
} else {
die("git checkout: updating paths is incompatible with switching branches/forcing");
}
}
if (source_tree)
read_tree_some(source_tree, pathspec);
else
read_cache();
return checkout_paths(pathspec);
}
if (new.name && !new.commit) {
die("Cannot switch branch to a non-commit.");
}
return switch_branches(&opts, &new);
}

View File

@ -205,7 +205,8 @@ static void create_base_index(void)
die("failed to unpack HEAD tree object");
parse_tree(tree);
init_tree_desc(&t, tree->buffer, tree->size);
unpack_trees(1, &t, &opts);
if (unpack_trees(1, &t, &opts))
exit(128); /* We've already reported the error, finish dying */
}
static char *prepare_index(int argc, const char **argv, const char *prefix)

View File

@ -7,6 +7,7 @@
#include "cache-tree.h"
#include "commit.h"
#include "blob.h"
#include "builtin.h"
#include "tree-walk.h"
#include "diff.h"
#include "diffcore.h"
@ -17,6 +18,7 @@
#include "xdiff-interface.h"
#include "interpolate.h"
#include "attr.h"
#include "merge-recursive.h"
static int subtree_merge;
@ -221,22 +223,11 @@ static int git_merge_trees(int index_only,
return rc;
}
static int unmerged_index(void)
{
int i;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce))
return 1;
}
return 0;
}
static struct tree *git_write_tree(void)
struct tree *write_tree_from_memory(void)
{
struct tree *result = NULL;
if (unmerged_index()) {
if (unmerged_cache()) {
int i;
output(0, "There are unmerged index entries:");
for (i = 0; i < active_nr; i++) {
@ -1496,12 +1487,12 @@ static int process_entry(const char *path, struct stage_data *entry,
return clean_merge;
}
static int merge_trees(struct tree *head,
struct tree *merge,
struct tree *common,
const char *branch1,
const char *branch2,
struct tree **result)
int merge_trees(struct tree *head,
struct tree *merge,
struct tree *common,
const char *branch1,
const char *branch2,
struct tree **result)
{
int code, clean;
@ -1523,7 +1514,7 @@ static int merge_trees(struct tree *head,
sha1_to_hex(head->object.sha1),
sha1_to_hex(merge->object.sha1));
if (unmerged_index()) {
if (unmerged_cache()) {
struct path_list *entries, *re_head, *re_merge;
int i;
path_list_clear(&current_file_set, 1);
@ -1553,7 +1544,7 @@ static int merge_trees(struct tree *head,
clean = 1;
if (index_only)
*result = git_write_tree();
*result = write_tree_from_memory();
return clean;
}
@ -1573,12 +1564,12 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)
* Merge the commits h1 and h2, return the resulting virtual
* commit object and a flag indicating the cleanness of the merge.
*/
static int merge(struct commit *h1,
struct commit *h2,
const char *branch1,
const char *branch2,
struct commit_list *ca,
struct commit **result)
int merge_recursive(struct commit *h1,
struct commit *h2,
const char *branch1,
const char *branch2,
struct commit_list *ca,
struct commit **result)
{
struct commit_list *iter;
struct commit *merged_common_ancestors;
@ -1623,11 +1614,11 @@ static int merge(struct commit *h1,
* "conflicts" were already resolved.
*/
discard_cache();
merge(merged_common_ancestors, iter->item,
"Temporary merge branch 1",
"Temporary merge branch 2",
NULL,
&merged_common_ancestors);
merge_recursive(merged_common_ancestors, iter->item,
"Temporary merge branch 1",
"Temporary merge branch 2",
NULL,
&merged_common_ancestors);
call_depth--;
if (!merged_common_ancestors)
@ -1698,7 +1689,7 @@ static int merge_config(const char *var, const char *value)
return git_default_config(var, value);
}
int main(int argc, char *argv[])
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
{
static const char *bases[20];
static unsigned bases_count = 0;
@ -1752,7 +1743,7 @@ int main(int argc, char *argv[])
struct commit *ancestor = get_ref(bases[i]);
ca = commit_list_insert(ancestor, &ca);
}
clean = merge(h1, h2, branch1, branch2, ca, &result);
clean = merge_recursive(h1, h2, branch1, branch2, ca, &result);
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||

View File

@ -269,7 +269,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
parse_tree(tree);
init_tree_desc(t+i, tree->buffer, tree->size);
}
unpack_trees(nr_trees, t, &opts);
if (unpack_trees(nr_trees, t, &opts))
return 128;
/*
* When reading only one tree (either the most basic form,

View File

@ -16,6 +16,7 @@
#include "diff.h"
#include "diffcore.h"
#include "tree.h"
#include "branch.h"
static const char builtin_reset_usage[] =
"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
@ -44,18 +45,6 @@ static inline int is_merge(void)
return !access(git_path("MERGE_HEAD"), F_OK);
}
static int unmerged_files(void)
{
int i;
read_cache();
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce))
return 1;
}
return 0;
}
static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
{
int i = 0;
@ -250,7 +239,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
* at all, but requires them in a good order. Other resets reset
* the index file to the tree object we are switching to. */
if (reset_type == SOFT) {
if (is_merge() || unmerged_files())
if (is_merge() || read_cache() < 0 || unmerged_cache())
die("Cannot do a soft reset in the middle of a merge.");
}
else if (reset_index_file(sha1, (reset_type == HARD)))
@ -282,10 +271,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
break;
}
unlink(git_path("MERGE_HEAD"));
unlink(git_path("rr-cache/MERGE_RR"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("SQUASH_MSG"));
remove_branch_state();
free(reflog_action);

View File

@ -18,6 +18,7 @@ extern int cmd_blame(int argc, const char **argv, const char *prefix);
extern int cmd_branch(int argc, const char **argv, const char *prefix);
extern int cmd_bundle(int argc, const char **argv, const char *prefix);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
extern int cmd_checkout(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
@ -56,6 +57,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);

View File

@ -252,6 +252,7 @@ extern struct index_state the_index;
#define read_cache_from(path) read_index_from(&the_index, (path))
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
#define discard_cache() discard_index(&the_index)
#define unmerged_cache() unmerged_index(&the_index)
#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
#define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
#define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
@ -346,6 +347,7 @@ extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path);
extern int write_index(struct index_state *, int newfd);
extern int discard_index(struct index_state *);
extern int unmerged_index(struct index_state *);
extern int verify_path(const char *path);
extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
extern int index_name_pos(struct index_state *, const char *name, int namelen);

View File

@ -737,7 +737,8 @@ int run_diff_index(struct rev_info *revs, int cached)
opts.unpack_data = revs;
init_tree_desc(&t, tree->buffer, tree->size);
unpack_trees(1, &t, &opts);
if (unpack_trees(1, &t, &opts))
exit(128);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
@ -789,6 +790,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
opts.unpack_data = &revs;
init_tree_desc(&t, tree->buffer, tree->size);
unpack_trees(1, &t, &opts);
if (unpack_trees(1, &t, &opts))
exit(128);
return 0;
}

3
git.c
View File

@ -278,6 +278,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "branch", cmd_branch, RUN_SETUP },
{ "bundle", cmd_bundle },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "check-ref-format", cmd_check_ref_format },
@ -321,6 +322,8 @@ static void handle_internal_command(int argc, const char **argv)
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
{ "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },

20
merge-recursive.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef MERGE_RECURSIVE_H
#define MERGE_RECURSIVE_H
int merge_recursive(struct commit *h1,
struct commit *h2,
const char *branch1,
const char *branch2,
struct commit_list *ancestors,
struct commit **result);
int merge_trees(struct tree *head,
struct tree *merge,
struct tree *common,
const char *branch1,
const char *branch2,
struct tree **result);
struct tree *write_tree_from_memory(void);
#endif

View File

@ -1166,6 +1166,16 @@ int discard_index(struct index_state *istate)
return 0;
}
int unmerged_index(struct index_state *istate)
{
int i;
for (i = 0; i < istate->cache_nr; i++) {
if (ce_stage(istate->cache[i]))
return 1;
}
return 0;
}
#define WRITE_BUFFER_SIZE 8192
static unsigned char write_buffer[WRITE_BUFFER_SIZE];
static unsigned long write_buffer_len;

32
t/t6029-merge-subtree.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
test_description='subtree merge strategy'
. ./test-lib.sh
test_expect_success setup '
s="1 2 3 4 5 6 7 8"
for i in $s; do echo $i; done >hello &&
git add hello &&
git commit -m initial &&
git checkout -b side &&
echo >>hello world &&
git add hello &&
git commit -m second &&
git checkout master &&
for i in mundo $s; do echo $i; done >hello &&
git add hello &&
git commit -m master
'
test_expect_success 'subtree available and works like recursive' '
git merge -s subtree side &&
for i in mundo $s world; do echo $i; done >expect &&
diff -u expect hello
'
test_done

View File

@ -83,13 +83,13 @@ test_expect_success "checkout with unrelated dirty tree without -m" '
fill 0 1 2 3 4 5 6 7 8 >same &&
cp same kept
git checkout side >messages &&
git diff same kept
diff -u same kept
(cat > messages.expect <<EOF
M same
EOF
) &&
touch messages.expect &&
git diff messages.expect messages
diff -u messages.expect messages
'
test_expect_success "checkout -m with dirty tree" '
@ -103,29 +103,22 @@ test_expect_success "checkout -m with dirty tree" '
test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
(cat >expect.messages <<EOF
Merging side with local
Merging:
ab76817 Side M one, D two, A three
virtual local
found 1 common ancestor(s):
7329388 Initial A one, A two
Auto-merged one
M one
EOF
) &&
git diff expect.messages messages &&
diff -u expect.messages messages &&
fill "M one" "A three" "D two" >expect.master &&
git diff --name-status master >current.master &&
diff expect.master current.master &&
diff -u expect.master current.master &&
fill "M one" >expect.side &&
git diff --name-status side >current.side &&
diff expect.side current.side &&
diff -u expect.side current.side &&
: >expect.index &&
git diff --cached >current.index &&
diff expect.index current.index
diff -u expect.index current.index
'
test_expect_success "checkout -m with dirty tree, renamed" '
@ -143,7 +136,7 @@ test_expect_success "checkout -m with dirty tree, renamed" '
git checkout -m renamer &&
fill 1 3 4 5 7 8 >expect &&
diff expect uno &&
diff -u expect uno &&
! test -f one &&
git diff --cached >current &&
! test -s current
@ -168,7 +161,7 @@ test_expect_success 'checkout -m with merge conflict' '
git diff master:one :3:uno |
sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
fill d2 aT d7 aS >expect &&
diff current expect &&
diff -u current expect &&
git diff --cached two >current &&
! test -s current
'
@ -185,7 +178,7 @@ If you want to create a new branch from this checkout, you may do so
HEAD is now at 7329388... Initial A one, A two
EOF
) &&
git diff messages.expect messages &&
diff -u messages.expect messages &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test "z$H" = "z$M" &&
@ -286,4 +279,38 @@ test_expect_success 'checkout with ambiguous tag/branch names' '
'
test_expect_success 'switch branches while in subdirectory' '
git reset --hard &&
git checkout master &&
mkdir subs &&
(
cd subs &&
git checkout side
) &&
! test -f subs/one &&
rm -fr subs
'
test_expect_success 'checkout specific path while in subdirectory' '
git reset --hard &&
git checkout side &&
mkdir subs &&
>subs/bero &&
git add subs/bero &&
git commit -m "add subs/bero" &&
git checkout master &&
mkdir -p subs &&
(
cd subs &&
git checkout side -- bero
) &&
test -f subs/bero
'
test_done

View File

@ -85,6 +85,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
int any_dirs = 0;
char *cache_name;
int ce_stage;
int skip_entry = 0;
/* Find the first name in the input. */
@ -122,13 +123,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
#if DBRT_DEBUG > 1
if (first)
printf("index %s\n", first);
fprintf(stderr, "index %s\n", first);
#endif
for (i = 0; i < len; i++) {
if (!posns[i] || posns[i] == df_conflict_list)
continue;
#if DBRT_DEBUG > 1
printf("%d %s\n", i + 1, posns[i]->name);
fprintf(stderr, "%d %s\n", i + 1, posns[i]->name);
#endif
if (!first || entcmp(first, firstdir,
posns[i]->name,
@ -153,6 +154,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
any_files = 1;
src[0] = active_cache[o->pos];
remove = o->pos;
if (o->skip_unmerged && ce_stage(src[0]))
skip_entry = 1;
}
for (i = 0; i < len; i++) {
@ -181,6 +184,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
continue;
}
if (skip_entry) {
subposns[i] = df_conflict_list;
posns[i] = posns[i]->next;
continue;
}
if (!o->merge)
ce_stage = 0;
else if (i + 1 < o->head_idx)
@ -205,23 +214,31 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
posns[i] = posns[i]->next;
}
if (any_files) {
if (o->merge) {
if (skip_entry) {
o->pos++;
while (o->pos < active_nr &&
!strcmp(active_cache[o->pos]->name,
src[0]->name))
o->pos++;
} else if (o->merge) {
int ret;
#if DBRT_DEBUG > 1
printf("%s:\n", first);
fprintf(stderr, "%s:\n", first);
for (i = 0; i < src_size; i++) {
printf(" %d ", i);
fprintf(stderr, " %d ", i);
if (src[i])
printf("%s\n", sha1_to_hex(src[i]->sha1));
fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1));
else
printf("\n");
fprintf(stderr, "\n");
}
#endif
ret = o->fn(src, o, remove);
if (ret < 0)
return ret;
#if DBRT_DEBUG > 1
printf("Added %d entries\n", ret);
fprintf(stderr, "Added %d entries\n", ret);
#endif
o->pos += ret;
} else {
@ -286,16 +303,16 @@ static void unlink_entry(char *name, char *last_symlink)
}
static struct checkout state;
static void check_updates(struct cache_entry **src, int nr,
struct unpack_trees_options *o)
static void check_updates(struct unpack_trees_options *o)
{
unsigned cnt = 0, total = 0;
struct progress *progress = NULL;
char last_symlink[PATH_MAX];
int i;
if (o->update && o->verbose_update) {
for (total = cnt = 0; cnt < nr; cnt++) {
struct cache_entry *ce = src[cnt];
for (total = cnt = 0; cnt < active_nr; cnt++) {
struct cache_entry *ce = active_cache[cnt];
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
total++;
}
@ -306,14 +323,16 @@ static void check_updates(struct cache_entry **src, int nr,
}
*last_symlink = '\0';
while (nr--) {
struct cache_entry *ce = *src++;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
display_progress(progress, ++cnt);
if (ce->ce_flags & CE_REMOVE) {
if (o->update)
unlink_entry(ce->name, last_symlink);
remove_cache_entry_at(i);
i--;
continue;
}
if (ce->ce_flags & CE_UPDATE) {
@ -354,23 +373,34 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
posns[i] = create_tree_entry_list(t+i);
if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
o, &df_conflict_list))
o, &df_conflict_list)) {
if (o->gently) {
discard_cache();
read_cache();
}
return -1;
}
}
if (o->trivial_merges_only && o->nontrivial_merge)
die("Merge requires file-level merging");
if (o->trivial_merges_only && o->nontrivial_merge) {
if (o->gently) {
discard_cache();
read_cache();
}
return o->gently ? -1 :
error("Merge requires file-level merging");
}
check_updates(active_cache, active_nr, o);
check_updates(o);
return 0;
}
/* Here come the merge functions */
static void reject_merge(struct cache_entry *ce)
static int reject_merge(struct cache_entry *ce)
{
die("Entry '%s' would be overwritten by merge. Cannot merge.",
ce->name);
return error("Entry '%s' would be overwritten by merge. Cannot merge.",
ce->name);
}
static int same(struct cache_entry *a, struct cache_entry *b)
@ -388,18 +418,18 @@ static int same(struct cache_entry *a, struct cache_entry *b)
* When a CE gets turned into an unmerged entry, we
* want it to be up-to-date
*/
static void verify_uptodate(struct cache_entry *ce,
static int verify_uptodate(struct cache_entry *ce,
struct unpack_trees_options *o)
{
struct stat st;
if (o->index_only || o->reset)
return;
return 0;
if (!lstat(ce->name, &st)) {
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
if (!changed)
return;
return 0;
/*
* NEEDSWORK: the current default policy is to allow
* submodule to be out of sync wrt the supermodule
@ -408,12 +438,13 @@ static void verify_uptodate(struct cache_entry *ce,
* checked out.
*/
if (S_ISGITLINK(ce->ce_mode))
return;
return 0;
errno = 0;
}
if (errno == ENOENT)
return;
die("Entry '%s' not uptodate. Cannot merge.", ce->name);
return 0;
return o->gently ? -1 :
error("Entry '%s' not uptodate. Cannot merge.", ce->name);
}
static void invalidate_ce_path(struct cache_entry *ce)
@ -479,7 +510,8 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
* ce->name is an entry in the subdirectory.
*/
if (!ce_stage(ce)) {
verify_uptodate(ce, o);
if (verify_uptodate(ce, o))
return -1;
ce->ce_flags |= CE_REMOVE;
}
cnt++;
@ -498,8 +530,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
d.exclude_per_dir = o->dir->exclude_per_dir;
i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
if (i)
die("Updating '%s' would lose untracked files in it",
ce->name);
return o->gently ? -1 :
error("Updating '%s' would lose untracked files in it",
ce->name);
free(pathbuf);
return cnt;
}
@ -508,16 +541,16 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
*/
static void verify_absent(struct cache_entry *ce, const char *action,
struct unpack_trees_options *o)
static int verify_absent(struct cache_entry *ce, const char *action,
struct unpack_trees_options *o)
{
struct stat st;
if (o->index_only || o->reset || !o->update)
return;
return 0;
if (has_symlink_leading_path(ce->name, NULL))
return;
return 0;
if (!lstat(ce->name, &st)) {
int cnt;
@ -528,7 +561,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
* ce->name is explicitly excluded, so it is Ok to
* overwrite it.
*/
return;
return 0;
if (S_ISDIR(st.st_mode)) {
/*
* We are checking out path "foo" and
@ -557,7 +590,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
* deleted entries here.
*/
o->pos += cnt;
return;
return 0;
}
/*
@ -569,12 +602,14 @@ static void verify_absent(struct cache_entry *ce, const char *action,
if (0 <= cnt) {
struct cache_entry *ce = active_cache[cnt];
if (ce->ce_flags & CE_REMOVE)
return;
return 0;
}
die("Untracked working tree file '%s' "
"would be %s by merge.", ce->name, action);
return o->gently ? -1 :
error("Untracked working tree file '%s' "
"would be %s by merge.", ce->name, action);
}
return 0;
}
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@ -592,12 +627,14 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
if (same(old, merge)) {
copy_cache_entry(merge, old);
} else {
verify_uptodate(old, o);
if (verify_uptodate(old, o))
return -1;
invalidate_ce_path(old);
}
}
else {
verify_absent(merge, "overwritten", o);
if (verify_absent(merge, "overwritten", o))
return -1;
invalidate_ce_path(merge);
}
@ -609,10 +646,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
struct unpack_trees_options *o)
{
if (old)
verify_uptodate(old, o);
else
verify_absent(ce, "removed", o);
if (old) {
if (verify_uptodate(old, o))
return -1;
} else
if (verify_absent(ce, "removed", o))
return -1;
ce->ce_flags |= CE_REMOVE;
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
invalidate_ce_path(ce);
@ -700,16 +739,15 @@ int threeway_merge(struct cache_entry **stages,
/* #14, #14ALT, #2ALT */
if (remote && !df_conflict_head && head_match && !remote_match) {
if (index && !same(index, remote) && !same(index, head))
reject_merge(index);
return o->gently ? -1 : reject_merge(index);
return merged_entry(remote, index, o);
}
/*
* If we have an entry in the index cache, then we want to
* make sure that it matches head.
*/
if (index && !same(index, head)) {
reject_merge(index);
}
if (index && !same(index, head))
return o->gently ? -1 : reject_merge(index);
if (head) {
/* #5ALT, #15 */
@ -759,8 +797,10 @@ int threeway_merge(struct cache_entry **stages,
remove_entry(remove);
if (index)
return deleted_entry(index, index, o);
else if (ce && !head_deleted)
verify_absent(ce, "removed", o);
else if (ce && !head_deleted) {
if (verify_absent(ce, "removed", o))
return -1;
}
return 0;
}
/*
@ -776,7 +816,8 @@ int threeway_merge(struct cache_entry **stages,
* conflict resolution files.
*/
if (index) {
verify_uptodate(index, o);
if (verify_uptodate(index, o))
return -1;
}
remove_entry(remove);
@ -856,11 +897,11 @@ int twoway_merge(struct cache_entry **src,
/* all other failures */
remove_entry(remove);
if (oldtree)
reject_merge(oldtree);
return o->gently ? -1 : reject_merge(oldtree);
if (current)
reject_merge(current);
return o->gently ? -1 : reject_merge(current);
if (newtree)
reject_merge(newtree);
return o->gently ? -1 : reject_merge(newtree);
return -1;
}
}
@ -887,7 +928,8 @@ int bind_merge(struct cache_entry **src,
return error("Cannot do a bind merge of %d trees\n",
o->merge_size);
if (a && old)
die("Entry '%s' overlaps. Cannot bind.", a->name);
return o->gently ? -1 :
error("Entry '%s' overlaps. Cannot bind.", a->name);
if (!a)
return keep_entry(old, o);
else

View File

@ -16,6 +16,8 @@ struct unpack_trees_options {
int trivial_merges_only;
int verbose_update;
int aggressive;
int skip_unmerged;
int gently;
const char *prefix;
int pos;
struct dir_struct *dir;