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:
commit
5a4d707a6d
15
Makefile
15
Makefile
@ -226,7 +226,7 @@ BASIC_CFLAGS =
|
|||||||
BASIC_LDFLAGS =
|
BASIC_LDFLAGS =
|
||||||
|
|
||||||
SCRIPT_SH = \
|
SCRIPT_SH = \
|
||||||
git-bisect.sh git-checkout.sh \
|
git-bisect.sh \
|
||||||
git-clone.sh \
|
git-clone.sh \
|
||||||
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
|
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
|
||||||
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
|
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
|
||||||
@ -265,23 +265,23 @@ PROGRAMS = \
|
|||||||
git-upload-pack$X \
|
git-upload-pack$X \
|
||||||
git-pack-redundant$X git-var$X \
|
git-pack-redundant$X git-var$X \
|
||||||
git-merge-tree$X git-imap-send$X \
|
git-merge-tree$X git-imap-send$X \
|
||||||
git-merge-recursive$X \
|
|
||||||
$(EXTRA_PROGRAMS)
|
$(EXTRA_PROGRAMS)
|
||||||
|
|
||||||
# Empty...
|
# Empty...
|
||||||
EXTRA_PROGRAMS =
|
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 = \
|
BUILT_INS = \
|
||||||
git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
|
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-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-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))
|
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
|
||||||
|
|
||||||
# what 'all' will build and 'install' will install, in gitexecdir
|
# what 'all' will build and 'install' will install, in gitexecdir
|
||||||
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
|
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
|
||||||
|
|
||||||
ALL_PROGRAMS += git-merge-subtree$X
|
|
||||||
|
|
||||||
# what 'all' will build but not install in gitexecdir
|
# what 'all' will build but not install in gitexecdir
|
||||||
OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
|
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) \
|
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 \
|
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 \
|
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
|
alias.o
|
||||||
|
|
||||||
BUILTIN_OBJS = \
|
BUILTIN_OBJS = \
|
||||||
@ -340,6 +340,7 @@ BUILTIN_OBJS = \
|
|||||||
builtin-bundle.o \
|
builtin-bundle.o \
|
||||||
builtin-cat-file.o \
|
builtin-cat-file.o \
|
||||||
builtin-check-attr.o \
|
builtin-check-attr.o \
|
||||||
|
builtin-checkout.o \
|
||||||
builtin-checkout-index.o \
|
builtin-checkout-index.o \
|
||||||
builtin-check-ref-format.o \
|
builtin-check-ref-format.o \
|
||||||
builtin-clean.o \
|
builtin-clean.o \
|
||||||
@ -370,6 +371,7 @@ BUILTIN_OBJS = \
|
|||||||
builtin-merge-base.o \
|
builtin-merge-base.o \
|
||||||
builtin-merge-file.o \
|
builtin-merge-file.o \
|
||||||
builtin-merge-ours.o \
|
builtin-merge-ours.o \
|
||||||
|
builtin-merge-recursive.o \
|
||||||
builtin-mv.o \
|
builtin-mv.o \
|
||||||
builtin-name-rev.o \
|
builtin-name-rev.o \
|
||||||
builtin-pack-objects.o \
|
builtin-pack-objects.o \
|
||||||
@ -840,9 +842,6 @@ help.o: help.c common-cmds.h GIT-CFLAGS
|
|||||||
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
|
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
|
||||||
'-DGIT_INFO_PATH="$(infodir_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
|
$(BUILT_INS): git$X
|
||||||
$(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
|
$(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
|
||||||
|
|
||||||
|
148
branch.c
Normal file
148
branch.c
Normal 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
24
branch.h
Normal 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
|
138
builtin-branch.c
138
builtin-branch.c
@ -12,6 +12,7 @@
|
|||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
|
#include "branch.h"
|
||||||
|
|
||||||
static const char * const builtin_branch_usage[] = {
|
static const char * const builtin_branch_usage[] = {
|
||||||
"git-branch [options] [-r | -a]",
|
"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);
|
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)
|
static void rename_branch(const char *oldname, const char *newname, int force)
|
||||||
{
|
{
|
||||||
char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
|
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))
|
else if (rename && (argc == 2))
|
||||||
rename_branch(argv[0], argv[1], rename > 1);
|
rename_branch(argv[0], argv[1], rename > 1);
|
||||||
else if (argc <= 2)
|
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);
|
force_create, reflog, track);
|
||||||
else
|
else
|
||||||
usage_with_options(builtin_branch_usage, options);
|
usage_with_options(builtin_branch_usage, options);
|
||||||
|
573
builtin-checkout.c
Normal file
573
builtin-checkout.c
Normal 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);
|
||||||
|
}
|
@ -205,7 +205,8 @@ static void create_base_index(void)
|
|||||||
die("failed to unpack HEAD tree object");
|
die("failed to unpack HEAD tree object");
|
||||||
parse_tree(tree);
|
parse_tree(tree);
|
||||||
init_tree_desc(&t, tree->buffer, tree->size);
|
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)
|
static char *prepare_index(int argc, const char **argv, const char *prefix)
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "cache-tree.h"
|
#include "cache-tree.h"
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
#include "blob.h"
|
#include "blob.h"
|
||||||
|
#include "builtin.h"
|
||||||
#include "tree-walk.h"
|
#include "tree-walk.h"
|
||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "diffcore.h"
|
#include "diffcore.h"
|
||||||
@ -17,6 +18,7 @@
|
|||||||
#include "xdiff-interface.h"
|
#include "xdiff-interface.h"
|
||||||
#include "interpolate.h"
|
#include "interpolate.h"
|
||||||
#include "attr.h"
|
#include "attr.h"
|
||||||
|
#include "merge-recursive.h"
|
||||||
|
|
||||||
static int subtree_merge;
|
static int subtree_merge;
|
||||||
|
|
||||||
@ -221,22 +223,11 @@ static int git_merge_trees(int index_only,
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int unmerged_index(void)
|
struct tree *write_tree_from_memory(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 *result = NULL;
|
struct tree *result = NULL;
|
||||||
|
|
||||||
if (unmerged_index()) {
|
if (unmerged_cache()) {
|
||||||
int i;
|
int i;
|
||||||
output(0, "There are unmerged index entries:");
|
output(0, "There are unmerged index entries:");
|
||||||
for (i = 0; i < active_nr; i++) {
|
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;
|
return clean_merge;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int merge_trees(struct tree *head,
|
int merge_trees(struct tree *head,
|
||||||
struct tree *merge,
|
struct tree *merge,
|
||||||
struct tree *common,
|
struct tree *common,
|
||||||
const char *branch1,
|
const char *branch1,
|
||||||
const char *branch2,
|
const char *branch2,
|
||||||
struct tree **result)
|
struct tree **result)
|
||||||
{
|
{
|
||||||
int code, clean;
|
int code, clean;
|
||||||
|
|
||||||
@ -1523,7 +1514,7 @@ static int merge_trees(struct tree *head,
|
|||||||
sha1_to_hex(head->object.sha1),
|
sha1_to_hex(head->object.sha1),
|
||||||
sha1_to_hex(merge->object.sha1));
|
sha1_to_hex(merge->object.sha1));
|
||||||
|
|
||||||
if (unmerged_index()) {
|
if (unmerged_cache()) {
|
||||||
struct path_list *entries, *re_head, *re_merge;
|
struct path_list *entries, *re_head, *re_merge;
|
||||||
int i;
|
int i;
|
||||||
path_list_clear(¤t_file_set, 1);
|
path_list_clear(¤t_file_set, 1);
|
||||||
@ -1553,7 +1544,7 @@ static int merge_trees(struct tree *head,
|
|||||||
clean = 1;
|
clean = 1;
|
||||||
|
|
||||||
if (index_only)
|
if (index_only)
|
||||||
*result = git_write_tree();
|
*result = write_tree_from_memory();
|
||||||
|
|
||||||
return clean;
|
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
|
* Merge the commits h1 and h2, return the resulting virtual
|
||||||
* commit object and a flag indicating the cleanness of the merge.
|
* commit object and a flag indicating the cleanness of the merge.
|
||||||
*/
|
*/
|
||||||
static int merge(struct commit *h1,
|
int merge_recursive(struct commit *h1,
|
||||||
struct commit *h2,
|
struct commit *h2,
|
||||||
const char *branch1,
|
const char *branch1,
|
||||||
const char *branch2,
|
const char *branch2,
|
||||||
struct commit_list *ca,
|
struct commit_list *ca,
|
||||||
struct commit **result)
|
struct commit **result)
|
||||||
{
|
{
|
||||||
struct commit_list *iter;
|
struct commit_list *iter;
|
||||||
struct commit *merged_common_ancestors;
|
struct commit *merged_common_ancestors;
|
||||||
@ -1623,11 +1614,11 @@ static int merge(struct commit *h1,
|
|||||||
* "conflicts" were already resolved.
|
* "conflicts" were already resolved.
|
||||||
*/
|
*/
|
||||||
discard_cache();
|
discard_cache();
|
||||||
merge(merged_common_ancestors, iter->item,
|
merge_recursive(merged_common_ancestors, iter->item,
|
||||||
"Temporary merge branch 1",
|
"Temporary merge branch 1",
|
||||||
"Temporary merge branch 2",
|
"Temporary merge branch 2",
|
||||||
NULL,
|
NULL,
|
||||||
&merged_common_ancestors);
|
&merged_common_ancestors);
|
||||||
call_depth--;
|
call_depth--;
|
||||||
|
|
||||||
if (!merged_common_ancestors)
|
if (!merged_common_ancestors)
|
||||||
@ -1698,7 +1689,7 @@ static int merge_config(const char *var, const char *value)
|
|||||||
return git_default_config(var, 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 const char *bases[20];
|
||||||
static unsigned bases_count = 0;
|
static unsigned bases_count = 0;
|
||||||
@ -1752,7 +1743,7 @@ int main(int argc, char *argv[])
|
|||||||
struct commit *ancestor = get_ref(bases[i]);
|
struct commit *ancestor = get_ref(bases[i]);
|
||||||
ca = commit_list_insert(ancestor, &ca);
|
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 &&
|
if (active_cache_changed &&
|
||||||
(write_cache(index_fd, active_cache, active_nr) ||
|
(write_cache(index_fd, active_cache, active_nr) ||
|
@ -269,7 +269,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
|||||||
parse_tree(tree);
|
parse_tree(tree);
|
||||||
init_tree_desc(t+i, tree->buffer, tree->size);
|
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,
|
* When reading only one tree (either the most basic form,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "diffcore.h"
|
#include "diffcore.h"
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
#include "branch.h"
|
||||||
|
|
||||||
static const char builtin_reset_usage[] =
|
static const char builtin_reset_usage[] =
|
||||||
"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
|
"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);
|
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)
|
static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
|
||||||
{
|
{
|
||||||
int i = 0;
|
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
|
* at all, but requires them in a good order. Other resets reset
|
||||||
* the index file to the tree object we are switching to. */
|
* the index file to the tree object we are switching to. */
|
||||||
if (reset_type == SOFT) {
|
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.");
|
die("Cannot do a soft reset in the middle of a merge.");
|
||||||
}
|
}
|
||||||
else if (reset_index_file(sha1, (reset_type == HARD)))
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
unlink(git_path("MERGE_HEAD"));
|
remove_branch_state();
|
||||||
unlink(git_path("rr-cache/MERGE_RR"));
|
|
||||||
unlink(git_path("MERGE_MSG"));
|
|
||||||
unlink(git_path("SQUASH_MSG"));
|
|
||||||
|
|
||||||
free(reflog_action);
|
free(reflog_action);
|
||||||
|
|
||||||
|
@ -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_branch(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_bundle(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_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_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_attr(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_check_ref_format(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_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_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_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_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_name_rev(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
|
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
|
||||||
|
2
cache.h
2
cache.h
@ -252,6 +252,7 @@ extern struct index_state the_index;
|
|||||||
#define read_cache_from(path) read_index_from(&the_index, (path))
|
#define read_cache_from(path) read_index_from(&the_index, (path))
|
||||||
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
|
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
|
||||||
#define discard_cache() discard_index(&the_index)
|
#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 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 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))
|
#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 read_index_from(struct index_state *, const char *path);
|
||||||
extern int write_index(struct index_state *, int newfd);
|
extern int write_index(struct index_state *, int newfd);
|
||||||
extern int discard_index(struct index_state *);
|
extern int discard_index(struct index_state *);
|
||||||
|
extern int unmerged_index(struct index_state *);
|
||||||
extern int verify_path(const char *path);
|
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_exists(struct index_state *istate, const char *name, int namelen);
|
||||||
extern int index_name_pos(struct index_state *, const char *name, int namelen);
|
extern int index_name_pos(struct index_state *, const char *name, int namelen);
|
||||||
|
@ -737,7 +737,8 @@ int run_diff_index(struct rev_info *revs, int cached)
|
|||||||
opts.unpack_data = revs;
|
opts.unpack_data = revs;
|
||||||
|
|
||||||
init_tree_desc(&t, tree->buffer, tree->size);
|
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);
|
diffcore_std(&revs->diffopt);
|
||||||
diff_flush(&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;
|
opts.unpack_data = &revs;
|
||||||
|
|
||||||
init_tree_desc(&t, tree->buffer, tree->size);
|
init_tree_desc(&t, tree->buffer, tree->size);
|
||||||
unpack_trees(1, &t, &opts);
|
if (unpack_trees(1, &t, &opts))
|
||||||
|
exit(128);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
3
git.c
3
git.c
@ -278,6 +278,7 @@ static void handle_internal_command(int argc, const char **argv)
|
|||||||
{ "branch", cmd_branch, RUN_SETUP },
|
{ "branch", cmd_branch, RUN_SETUP },
|
||||||
{ "bundle", cmd_bundle },
|
{ "bundle", cmd_bundle },
|
||||||
{ "cat-file", cmd_cat_file, RUN_SETUP },
|
{ "cat-file", cmd_cat_file, RUN_SETUP },
|
||||||
|
{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
|
||||||
{ "checkout-index", cmd_checkout_index,
|
{ "checkout-index", cmd_checkout_index,
|
||||||
RUN_SETUP | NEED_WORK_TREE},
|
RUN_SETUP | NEED_WORK_TREE},
|
||||||
{ "check-ref-format", cmd_check_ref_format },
|
{ "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-base", cmd_merge_base, RUN_SETUP },
|
||||||
{ "merge-file", cmd_merge_file },
|
{ "merge-file", cmd_merge_file },
|
||||||
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
|
{ "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 },
|
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
|
||||||
{ "name-rev", cmd_name_rev, RUN_SETUP },
|
{ "name-rev", cmd_name_rev, RUN_SETUP },
|
||||||
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
|
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
|
||||||
|
20
merge-recursive.h
Normal file
20
merge-recursive.h
Normal 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
|
10
read-cache.c
10
read-cache.c
@ -1166,6 +1166,16 @@ int discard_index(struct index_state *istate)
|
|||||||
return 0;
|
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
|
#define WRITE_BUFFER_SIZE 8192
|
||||||
static unsigned char write_buffer[WRITE_BUFFER_SIZE];
|
static unsigned char write_buffer[WRITE_BUFFER_SIZE];
|
||||||
static unsigned long write_buffer_len;
|
static unsigned long write_buffer_len;
|
||||||
|
32
t/t6029-merge-subtree.sh
Executable file
32
t/t6029-merge-subtree.sh
Executable 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
|
@ -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 &&
|
fill 0 1 2 3 4 5 6 7 8 >same &&
|
||||||
cp same kept
|
cp same kept
|
||||||
git checkout side >messages &&
|
git checkout side >messages &&
|
||||||
git diff same kept
|
diff -u same kept
|
||||||
(cat > messages.expect <<EOF
|
(cat > messages.expect <<EOF
|
||||||
M same
|
M same
|
||||||
EOF
|
EOF
|
||||||
) &&
|
) &&
|
||||||
touch messages.expect &&
|
touch messages.expect &&
|
||||||
git diff messages.expect messages
|
diff -u messages.expect messages
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success "checkout -m with dirty tree" '
|
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" &&
|
test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
|
||||||
|
|
||||||
(cat >expect.messages <<EOF
|
(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
|
M one
|
||||||
EOF
|
EOF
|
||||||
) &&
|
) &&
|
||||||
git diff expect.messages messages &&
|
diff -u expect.messages messages &&
|
||||||
|
|
||||||
fill "M one" "A three" "D two" >expect.master &&
|
fill "M one" "A three" "D two" >expect.master &&
|
||||||
git diff --name-status master >current.master &&
|
git diff --name-status master >current.master &&
|
||||||
diff expect.master current.master &&
|
diff -u expect.master current.master &&
|
||||||
|
|
||||||
fill "M one" >expect.side &&
|
fill "M one" >expect.side &&
|
||||||
git diff --name-status side >current.side &&
|
git diff --name-status side >current.side &&
|
||||||
diff expect.side current.side &&
|
diff -u expect.side current.side &&
|
||||||
|
|
||||||
: >expect.index &&
|
: >expect.index &&
|
||||||
git diff --cached >current.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" '
|
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 &&
|
git checkout -m renamer &&
|
||||||
fill 1 3 4 5 7 8 >expect &&
|
fill 1 3 4 5 7 8 >expect &&
|
||||||
diff expect uno &&
|
diff -u expect uno &&
|
||||||
! test -f one &&
|
! test -f one &&
|
||||||
git diff --cached >current &&
|
git diff --cached >current &&
|
||||||
! test -s current
|
! test -s current
|
||||||
@ -168,7 +161,7 @@ test_expect_success 'checkout -m with merge conflict' '
|
|||||||
git diff master:one :3:uno |
|
git diff master:one :3:uno |
|
||||||
sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
|
sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
|
||||||
fill d2 aT d7 aS >expect &&
|
fill d2 aT d7 aS >expect &&
|
||||||
diff current expect &&
|
diff -u current expect &&
|
||||||
git diff --cached two >current &&
|
git diff --cached two >current &&
|
||||||
! test -s 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
|
HEAD is now at 7329388... Initial A one, A two
|
||||||
EOF
|
EOF
|
||||||
) &&
|
) &&
|
||||||
git diff messages.expect messages &&
|
diff -u messages.expect messages &&
|
||||||
H=$(git rev-parse --verify HEAD) &&
|
H=$(git rev-parse --verify HEAD) &&
|
||||||
M=$(git show-ref -s --verify refs/heads/master) &&
|
M=$(git show-ref -s --verify refs/heads/master) &&
|
||||||
test "z$H" = "z$M" &&
|
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
|
test_done
|
||||||
|
154
unpack-trees.c
154
unpack-trees.c
@ -85,6 +85,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
|
|||||||
int any_dirs = 0;
|
int any_dirs = 0;
|
||||||
char *cache_name;
|
char *cache_name;
|
||||||
int ce_stage;
|
int ce_stage;
|
||||||
|
int skip_entry = 0;
|
||||||
|
|
||||||
/* Find the first name in the input. */
|
/* 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 DBRT_DEBUG > 1
|
||||||
if (first)
|
if (first)
|
||||||
printf("index %s\n", first);
|
fprintf(stderr, "index %s\n", first);
|
||||||
#endif
|
#endif
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
if (!posns[i] || posns[i] == df_conflict_list)
|
if (!posns[i] || posns[i] == df_conflict_list)
|
||||||
continue;
|
continue;
|
||||||
#if DBRT_DEBUG > 1
|
#if DBRT_DEBUG > 1
|
||||||
printf("%d %s\n", i + 1, posns[i]->name);
|
fprintf(stderr, "%d %s\n", i + 1, posns[i]->name);
|
||||||
#endif
|
#endif
|
||||||
if (!first || entcmp(first, firstdir,
|
if (!first || entcmp(first, firstdir,
|
||||||
posns[i]->name,
|
posns[i]->name,
|
||||||
@ -153,6 +154,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
|
|||||||
any_files = 1;
|
any_files = 1;
|
||||||
src[0] = active_cache[o->pos];
|
src[0] = active_cache[o->pos];
|
||||||
remove = o->pos;
|
remove = o->pos;
|
||||||
|
if (o->skip_unmerged && ce_stage(src[0]))
|
||||||
|
skip_entry = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
@ -181,6 +184,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skip_entry) {
|
||||||
|
subposns[i] = df_conflict_list;
|
||||||
|
posns[i] = posns[i]->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!o->merge)
|
if (!o->merge)
|
||||||
ce_stage = 0;
|
ce_stage = 0;
|
||||||
else if (i + 1 < o->head_idx)
|
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;
|
posns[i] = posns[i]->next;
|
||||||
}
|
}
|
||||||
if (any_files) {
|
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;
|
int ret;
|
||||||
|
|
||||||
#if DBRT_DEBUG > 1
|
#if DBRT_DEBUG > 1
|
||||||
printf("%s:\n", first);
|
fprintf(stderr, "%s:\n", first);
|
||||||
for (i = 0; i < src_size; i++) {
|
for (i = 0; i < src_size; i++) {
|
||||||
printf(" %d ", i);
|
fprintf(stderr, " %d ", i);
|
||||||
if (src[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
|
else
|
||||||
printf("\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
ret = o->fn(src, o, remove);
|
ret = o->fn(src, o, remove);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
#if DBRT_DEBUG > 1
|
#if DBRT_DEBUG > 1
|
||||||
printf("Added %d entries\n", ret);
|
fprintf(stderr, "Added %d entries\n", ret);
|
||||||
#endif
|
#endif
|
||||||
o->pos += ret;
|
o->pos += ret;
|
||||||
} else {
|
} else {
|
||||||
@ -286,16 +303,16 @@ static void unlink_entry(char *name, char *last_symlink)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct checkout state;
|
static struct checkout state;
|
||||||
static void check_updates(struct cache_entry **src, int nr,
|
static void check_updates(struct unpack_trees_options *o)
|
||||||
struct unpack_trees_options *o)
|
|
||||||
{
|
{
|
||||||
unsigned cnt = 0, total = 0;
|
unsigned cnt = 0, total = 0;
|
||||||
struct progress *progress = NULL;
|
struct progress *progress = NULL;
|
||||||
char last_symlink[PATH_MAX];
|
char last_symlink[PATH_MAX];
|
||||||
|
int i;
|
||||||
|
|
||||||
if (o->update && o->verbose_update) {
|
if (o->update && o->verbose_update) {
|
||||||
for (total = cnt = 0; cnt < nr; cnt++) {
|
for (total = cnt = 0; cnt < active_nr; cnt++) {
|
||||||
struct cache_entry *ce = src[cnt];
|
struct cache_entry *ce = active_cache[cnt];
|
||||||
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
|
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
|
||||||
total++;
|
total++;
|
||||||
}
|
}
|
||||||
@ -306,14 +323,16 @@ static void check_updates(struct cache_entry **src, int nr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
*last_symlink = '\0';
|
*last_symlink = '\0';
|
||||||
while (nr--) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
struct cache_entry *ce = *src++;
|
struct cache_entry *ce = active_cache[i];
|
||||||
|
|
||||||
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
|
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
|
||||||
display_progress(progress, ++cnt);
|
display_progress(progress, ++cnt);
|
||||||
if (ce->ce_flags & CE_REMOVE) {
|
if (ce->ce_flags & CE_REMOVE) {
|
||||||
if (o->update)
|
if (o->update)
|
||||||
unlink_entry(ce->name, last_symlink);
|
unlink_entry(ce->name, last_symlink);
|
||||||
|
remove_cache_entry_at(i);
|
||||||
|
i--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ce->ce_flags & CE_UPDATE) {
|
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);
|
posns[i] = create_tree_entry_list(t+i);
|
||||||
|
|
||||||
if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
|
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;
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o->trivial_merges_only && o->nontrivial_merge)
|
if (o->trivial_merges_only && o->nontrivial_merge) {
|
||||||
die("Merge requires file-level merging");
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Here come the merge functions */
|
/* 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.",
|
return error("Entry '%s' would be overwritten by merge. Cannot merge.",
|
||||||
ce->name);
|
ce->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int same(struct cache_entry *a, struct cache_entry *b)
|
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
|
* When a CE gets turned into an unmerged entry, we
|
||||||
* want it to be up-to-date
|
* 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 unpack_trees_options *o)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (o->index_only || o->reset)
|
if (o->index_only || o->reset)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
if (!lstat(ce->name, &st)) {
|
if (!lstat(ce->name, &st)) {
|
||||||
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
|
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
|
||||||
if (!changed)
|
if (!changed)
|
||||||
return;
|
return 0;
|
||||||
/*
|
/*
|
||||||
* NEEDSWORK: the current default policy is to allow
|
* NEEDSWORK: the current default policy is to allow
|
||||||
* submodule to be out of sync wrt the supermodule
|
* submodule to be out of sync wrt the supermodule
|
||||||
@ -408,12 +438,13 @@ static void verify_uptodate(struct cache_entry *ce,
|
|||||||
* checked out.
|
* checked out.
|
||||||
*/
|
*/
|
||||||
if (S_ISGITLINK(ce->ce_mode))
|
if (S_ISGITLINK(ce->ce_mode))
|
||||||
return;
|
return 0;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
}
|
}
|
||||||
if (errno == ENOENT)
|
if (errno == ENOENT)
|
||||||
return;
|
return 0;
|
||||||
die("Entry '%s' not uptodate. Cannot merge.", ce->name);
|
return o->gently ? -1 :
|
||||||
|
error("Entry '%s' not uptodate. Cannot merge.", ce->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void invalidate_ce_path(struct cache_entry *ce)
|
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.
|
* ce->name is an entry in the subdirectory.
|
||||||
*/
|
*/
|
||||||
if (!ce_stage(ce)) {
|
if (!ce_stage(ce)) {
|
||||||
verify_uptodate(ce, o);
|
if (verify_uptodate(ce, o))
|
||||||
|
return -1;
|
||||||
ce->ce_flags |= CE_REMOVE;
|
ce->ce_flags |= CE_REMOVE;
|
||||||
}
|
}
|
||||||
cnt++;
|
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;
|
d.exclude_per_dir = o->dir->exclude_per_dir;
|
||||||
i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
|
i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
|
||||||
if (i)
|
if (i)
|
||||||
die("Updating '%s' would lose untracked files in it",
|
return o->gently ? -1 :
|
||||||
ce->name);
|
error("Updating '%s' would lose untracked files in it",
|
||||||
|
ce->name);
|
||||||
free(pathbuf);
|
free(pathbuf);
|
||||||
return cnt;
|
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
|
* We do not want to remove or overwrite a working tree file that
|
||||||
* is not tracked, unless it is ignored.
|
* is not tracked, unless it is ignored.
|
||||||
*/
|
*/
|
||||||
static void verify_absent(struct cache_entry *ce, const char *action,
|
static int verify_absent(struct cache_entry *ce, const char *action,
|
||||||
struct unpack_trees_options *o)
|
struct unpack_trees_options *o)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (o->index_only || o->reset || !o->update)
|
if (o->index_only || o->reset || !o->update)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
if (has_symlink_leading_path(ce->name, NULL))
|
if (has_symlink_leading_path(ce->name, NULL))
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
if (!lstat(ce->name, &st)) {
|
if (!lstat(ce->name, &st)) {
|
||||||
int cnt;
|
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
|
* ce->name is explicitly excluded, so it is Ok to
|
||||||
* overwrite it.
|
* overwrite it.
|
||||||
*/
|
*/
|
||||||
return;
|
return 0;
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
/*
|
/*
|
||||||
* We are checking out path "foo" and
|
* 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.
|
* deleted entries here.
|
||||||
*/
|
*/
|
||||||
o->pos += cnt;
|
o->pos += cnt;
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -569,12 +602,14 @@ static void verify_absent(struct cache_entry *ce, const char *action,
|
|||||||
if (0 <= cnt) {
|
if (0 <= cnt) {
|
||||||
struct cache_entry *ce = active_cache[cnt];
|
struct cache_entry *ce = active_cache[cnt];
|
||||||
if (ce->ce_flags & CE_REMOVE)
|
if (ce->ce_flags & CE_REMOVE)
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
die("Untracked working tree file '%s' "
|
return o->gently ? -1 :
|
||||||
"would be %s by merge.", ce->name, action);
|
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,
|
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)) {
|
if (same(old, merge)) {
|
||||||
copy_cache_entry(merge, old);
|
copy_cache_entry(merge, old);
|
||||||
} else {
|
} else {
|
||||||
verify_uptodate(old, o);
|
if (verify_uptodate(old, o))
|
||||||
|
return -1;
|
||||||
invalidate_ce_path(old);
|
invalidate_ce_path(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
verify_absent(merge, "overwritten", o);
|
if (verify_absent(merge, "overwritten", o))
|
||||||
|
return -1;
|
||||||
invalidate_ce_path(merge);
|
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,
|
static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
|
||||||
struct unpack_trees_options *o)
|
struct unpack_trees_options *o)
|
||||||
{
|
{
|
||||||
if (old)
|
if (old) {
|
||||||
verify_uptodate(old, o);
|
if (verify_uptodate(old, o))
|
||||||
else
|
return -1;
|
||||||
verify_absent(ce, "removed", o);
|
} else
|
||||||
|
if (verify_absent(ce, "removed", o))
|
||||||
|
return -1;
|
||||||
ce->ce_flags |= CE_REMOVE;
|
ce->ce_flags |= CE_REMOVE;
|
||||||
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
|
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
|
||||||
invalidate_ce_path(ce);
|
invalidate_ce_path(ce);
|
||||||
@ -700,16 +739,15 @@ int threeway_merge(struct cache_entry **stages,
|
|||||||
/* #14, #14ALT, #2ALT */
|
/* #14, #14ALT, #2ALT */
|
||||||
if (remote && !df_conflict_head && head_match && !remote_match) {
|
if (remote && !df_conflict_head && head_match && !remote_match) {
|
||||||
if (index && !same(index, remote) && !same(index, head))
|
if (index && !same(index, remote) && !same(index, head))
|
||||||
reject_merge(index);
|
return o->gently ? -1 : reject_merge(index);
|
||||||
return merged_entry(remote, index, o);
|
return merged_entry(remote, index, o);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* If we have an entry in the index cache, then we want to
|
* If we have an entry in the index cache, then we want to
|
||||||
* make sure that it matches head.
|
* make sure that it matches head.
|
||||||
*/
|
*/
|
||||||
if (index && !same(index, head)) {
|
if (index && !same(index, head))
|
||||||
reject_merge(index);
|
return o->gently ? -1 : reject_merge(index);
|
||||||
}
|
|
||||||
|
|
||||||
if (head) {
|
if (head) {
|
||||||
/* #5ALT, #15 */
|
/* #5ALT, #15 */
|
||||||
@ -759,8 +797,10 @@ int threeway_merge(struct cache_entry **stages,
|
|||||||
remove_entry(remove);
|
remove_entry(remove);
|
||||||
if (index)
|
if (index)
|
||||||
return deleted_entry(index, index, o);
|
return deleted_entry(index, index, o);
|
||||||
else if (ce && !head_deleted)
|
else if (ce && !head_deleted) {
|
||||||
verify_absent(ce, "removed", o);
|
if (verify_absent(ce, "removed", o))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -776,7 +816,8 @@ int threeway_merge(struct cache_entry **stages,
|
|||||||
* conflict resolution files.
|
* conflict resolution files.
|
||||||
*/
|
*/
|
||||||
if (index) {
|
if (index) {
|
||||||
verify_uptodate(index, o);
|
if (verify_uptodate(index, o))
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_entry(remove);
|
remove_entry(remove);
|
||||||
@ -856,11 +897,11 @@ int twoway_merge(struct cache_entry **src,
|
|||||||
/* all other failures */
|
/* all other failures */
|
||||||
remove_entry(remove);
|
remove_entry(remove);
|
||||||
if (oldtree)
|
if (oldtree)
|
||||||
reject_merge(oldtree);
|
return o->gently ? -1 : reject_merge(oldtree);
|
||||||
if (current)
|
if (current)
|
||||||
reject_merge(current);
|
return o->gently ? -1 : reject_merge(current);
|
||||||
if (newtree)
|
if (newtree)
|
||||||
reject_merge(newtree);
|
return o->gently ? -1 : reject_merge(newtree);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -887,7 +928,8 @@ int bind_merge(struct cache_entry **src,
|
|||||||
return error("Cannot do a bind merge of %d trees\n",
|
return error("Cannot do a bind merge of %d trees\n",
|
||||||
o->merge_size);
|
o->merge_size);
|
||||||
if (a && old)
|
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)
|
if (!a)
|
||||||
return keep_entry(old, o);
|
return keep_entry(old, o);
|
||||||
else
|
else
|
||||||
|
@ -16,6 +16,8 @@ struct unpack_trees_options {
|
|||||||
int trivial_merges_only;
|
int trivial_merges_only;
|
||||||
int verbose_update;
|
int verbose_update;
|
||||||
int aggressive;
|
int aggressive;
|
||||||
|
int skip_unmerged;
|
||||||
|
int gently;
|
||||||
const char *prefix;
|
const char *prefix;
|
||||||
int pos;
|
int pos;
|
||||||
struct dir_struct *dir;
|
struct dir_struct *dir;
|
||||||
|
Loading…
Reference in New Issue
Block a user