From: Junio C Hamano Date: Wed, 13 Jan 2010 20:31:13 +0000 (-0800) Subject: Merge branch 'jc/checkout-merge-base' X-Git-Tag: v1.7.0-rc0~99 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/1f73566af5bec28cd8489c6139a9ede95817349c?ds=inline;hp=-c Merge branch 'jc/checkout-merge-base' * jc/checkout-merge-base: rebase -i: teach --onto A...B syntax rebase: fix --onto A...B parsing and add tests "rebase --onto A...B" replays history on the merge base between A and B "checkout A...B" switches to the merge base between A and B --- 1f73566af5bec28cd8489c6139a9ede95817349c diff --combined builtin-checkout.c index 2708669383,fe7c8584ca..793542eb06 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@@ -302,9 -302,8 +302,9 @@@ static void show_local_changes(struct o static void describe_detached_head(char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); @@@ -397,7 -396,7 +397,7 @@@ static int merge_working_tree(struct ch topts.initial_checkout = is_cache_unborn(); topts.update = 1; topts.merge = 1; - topts.gently = opts->merge; + topts.gently = opts->merge && old->commit; topts.verbose_update = !opts->quiet; topts.fn = twoway_merge; topts.dir = xcalloc(1, sizeof(*topts.dir)); @@@ -422,13 -421,7 +422,13 @@@ struct merge_options o; if (!opts->merge) return 1; - parse_commit(old->commit); + + /* + * Without old->commit, the below is the same as + * the two-tree unpack we already tried and failed. + */ + if (!old->commit) + return 1; /* Do more real merge */ @@@ -696,7 -689,10 +696,10 @@@ int cmd_checkout(int argc, const char * * case 3: git checkout [] * * With no paths, if is a commit, that is to - * switch to the branch or detach HEAD at it. + * switch to the branch or detach HEAD at it. As a special case, + * if is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. * * With no paths, if is _not_ a commit, no -t nor -b * was given, and there is a tracking branch whose name is @@@ -722,7 -718,7 +725,7 @@@ if (!strcmp(arg, "-")) arg = "@{-1}"; - if (get_sha1(arg, rev)) { + if (get_sha1_mb(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); if (!patch_mode && diff --combined cache.h index 3e52c4efb1,f8df15a060..cf36c8160e --- a/cache.h +++ b/cache.h @@@ -177,20 -177,15 +177,20 @@@ struct cache_entry #define CE_HASHED (0x100000) #define CE_UNHASHED (0x200000) +#define CE_CONFLICTED (0x800000) + +/* Only remove in work directory, not index */ +#define CE_WT_REMOVE (0x400000) /* * Extended on-disk flags */ #define CE_INTENT_TO_ADD 0x20000000 +#define CE_SKIP_WORKTREE 0x40000000 /* CE_EXTENDED2 is for future extension */ #define CE_EXTENDED2 0x80000000 -#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD) +#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE) /* * Safeguard to avoid saving wrong flags: @@@ -239,7 -234,6 +239,7 @@@ static inline size_t ce_namelen(const s ondisk_cache_entry_size(ce_namelen(ce))) #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE) #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) @@@ -375,12 -369,9 +375,12 @@@ static inline enum object_type object_t #define CONFIG_ENVIRONMENT "GIT_CONFIG" #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH" #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES" +#define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS" #define GITATTRIBUTES_FILE ".gitattributes" #define INFOATTRIBUTES_FILE "info/attributes" #define ATTRIBUTE_MACRO_PREFIX "[attr]" +#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" extern int is_bare_repository_cfg; extern int is_bare_repository(void); @@@ -470,9 -461,7 +470,9 @@@ extern int index_name_is_other(const st /* do stat comparison even if CE_VALID is true */ #define CE_MATCH_IGNORE_VALID 01 /* do not check the contents but report dirty on racily-clean entries */ -#define CE_MATCH_RACY_IS_DIRTY 02 +#define CE_MATCH_RACY_IS_DIRTY 02 +/* do stat comparison even if CE_SKIP_WORKTREE is true */ +#define CE_MATCH_IGNORE_SKIP_WORKTREE 04 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); @@@ -537,7 -526,6 +537,7 @@@ extern int auto_crlf extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; +extern int core_apply_sparse_checkout; enum safe_crlf { SAFE_CRLF_FALSE = 0, @@@ -580,8 -568,6 +580,8 @@@ enum object_creation_mode extern enum object_creation_mode object_creation_mode; +extern char *notes_ref_name; + extern int grafts_replace_parents; #define GIT_REPO_VERSION 0 @@@ -659,7 -645,6 +659,7 @@@ int set_shared_perm(const char *path, i #define adjust_shared_perm(path) set_shared_perm((path), 0) int safe_create_leading_directories(char *path); int safe_create_leading_directories_const(const char *path); +extern char *expand_user_path(const char *path); char *enter_repo(char *path, int strict); static inline int is_absolute_path(const char *path) { @@@ -672,7 -657,6 +672,7 @@@ const char *make_relative_path(const ch int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); +int daemon_avoid_alias(const char *path); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); @@@ -711,11 -695,7 +711,11 @@@ static inline unsigned int hexval(unsig #define DEFAULT_ABBREV 7 extern int get_sha1(const char *str, unsigned char *sha1); -extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode); +extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix); +static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) +{ + return get_sha1_with_mode_1(str, sha1, mode, 1, NULL); +} extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); @@@ -723,6 -703,7 +723,7 @@@ extern const char *resolve_ref(const ch extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); extern int interpret_branch_name(const char *str, struct strbuf *); + extern int get_sha1_mb(const char *str, unsigned char *sha1); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; @@@ -771,8 -752,6 +772,8 @@@ extern const char *git_author_info(int) extern const char *git_committer_info(int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); extern const char *fmt_name(const char *name, const char *email); +extern const char *git_editor(void); +extern const char *git_pager(void); struct checkout { const char *base_dir; @@@ -879,6 -858,7 +880,6 @@@ extern struct ref *find_ref_by_name(con extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern int finish_connect(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); -extern int get_ack(int fd, unsigned char *result_sha1); struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; @@@ -924,7 -904,6 +925,7 @@@ extern unsigned long git_config_ulong(c extern int git_config_bool_or_int(const char *, const char *, int *); extern int git_config_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); +extern int git_config_pathname(const char **, const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); @@@ -983,9 -962,7 +984,9 @@@ extern void *alloc_object_node(void) extern void alloc_report(void); /* trace.c */ +__attribute__((format (printf, 1, 2))) extern void trace_printf(const char *format, ...); +__attribute__((format (printf, 2, 3))) extern void trace_argv_printf(const char **argv, const char *format, ...); /* convert.c */ diff --combined git-rebase--interactive.sh index 6ed57e2664,f7ae02ccb5..1560e84bd5 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@@ -106,8 -106,8 +106,8 @@@ mark_action_done () sed -e 1q < "$TODO" >> "$DONE" sed -e 1d < "$TODO" >> "$TODO".new mv -f "$TODO".new "$TODO" - count=$(grep -c '^[^#]' < "$DONE") - total=$(($count+$(grep -c '^[^#]' < "$TODO"))) + count=$(sane_grep -c '^[^#]' < "$DONE") + total=$(($count+$(sane_grep -c '^[^#]' < "$TODO"))) if test "$last_count" != "$count" then last_count=$count @@@ -147,7 -147,7 +147,7 @@@ die_abort () } has_action () { - grep '^[^#]' "$1" >/dev/null + sane_grep '^[^#]' "$1" >/dev/null } pick_one () { @@@ -168,7 -168,7 +168,7 @@@ output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) - output warn Fast forward to $sha1 + output warn Fast-forward to $sha1 else output git cherry-pick "$@" fi @@@ -248,9 -248,9 +248,9 @@@ pick_one_preserving_merges () done case $fast_forward in t) - output warn "Fast forward to $sha1" + output warn "Fast-forward to $sha1" output git reset --hard $sha1 || - die "Cannot fast forward to $sha1" + die "Cannot fast-forward to $sha1" ;; f) first_parent=$(expr "$new_parents" : ' \([^ ]*\)') @@@ -322,7 -322,7 +322,7 @@@ make_squash_message () } peek_next_command () { - sed -n "1s/ .*$//p" < "$TODO" + sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO" } do_next () { @@@ -340,14 -340,6 +340,14 @@@ pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" ;; + reword|r) + comment_for_reflog reword + + mark_action_done + pick_one $sha1 || + die_with_patch $sha1 "Could not apply $sha1... $rest" + git commit --amend + ;; edit|e) comment_for_reflog edit @@@ -416,12 -408,7 +416,12 @@@ ;; *) warn "Unknown command: $command $sha1 $rest" - die_with_patch $sha1 "Please fix this in the file $TODO." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "Please fix this in the file $TODO." + else + die "Please fix this in the file $TODO." + fi ;; esac test -s "$TODO" && return @@@ -495,6 -482,25 +495,25 @@@ get_saved_options () test -f "$DOTEST"/rebase-root && REBASE_ROOT=t } + LF=' + ' + parse_onto () { + case "$1" in + *...*) + if left=${1%...*} right=${1#*...} && + onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) + then + case "$onto" in + ?*"$LF"?* | '') + exit 1 ;; + esac + echo "$onto" + exit 0 + fi + esac + git rev-parse --verify "$1^0" + } + while test $# != 0 do case "$1" in @@@ -602,7 -608,7 +621,7 @@@ first and then run 'git rebase --contin ;; --onto) shift - ONTO=$(git rev-parse --verify "$1") || + ONTO=$(parse_onto "$1") || die "Does not point to a valid commit: $1" ;; --) @@@ -744,7 -750,7 +763,7 @@@ git rev-list $REVISIONS | while read rev do - if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = "" + if test -f "$REWRITTEN"/$rev -a "$(sane_grep "$rev" "$DOTEST"/not-cherry-picks)" = "" then # Use -f2 because if rev-list is telling us this commit is # not worthwhile, we don't want to track its multiple heads, @@@ -752,7 -758,7 +771,7 @@@ # be rebasing on top of it git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) - grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" + sane_grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" rm "$REWRITTEN"/$rev fi done @@@ -765,7 -771,6 +784,7 @@@ # # Commands: # p, pick = use commit +# r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # @@@ -779,7 -784,7 +798,7 @@@ EO cp "$TODO" "$TODO".backup git_editor "$TODO" || - die "Could not execute editor" + die_abort "Could not execute editor" has_action "$TODO" || die_abort "Nothing to do" diff --combined git-rebase.sh index b121f4537c,9bd89746ab..3de0942c0f --- a/git-rebase.sh +++ b/git-rebase.sh @@@ -34,6 -34,8 +34,8 @@@ set_reflog_action rebas require_work_tree cd_to_toplevel + LF=' + ' OK_TO_SKIP_PRE_REBASE= RESOLVEMSG=" When you have resolved this problem run \"git rebase --continue\". @@@ -417,7 -419,27 +419,27 @@@ f # Make sure the branch to rebase onto is valid. onto_name=${newbase-"$upstream_name"} - onto=$(git rev-parse --verify "${onto_name}^0") || exit + case "$onto_name" in + *...*) + if left=${onto_name%...*} right=${onto_name#*...} && + onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) + then + case "$onto" in + ?*"$LF"?*) + die "$onto_name: there are more than one merge bases" + ;; + '') + die "$onto_name: there is no merge base" + ;; + esac + else + die "$onto_name: there is no merge base" + fi + ;; + *) + onto=$(git rev-parse --verify "${onto_name}^0") || exit + ;; + esac # If a hook exists, give it a chance to interrupt run_pre_rebase_hook "$upstream_arg" "$@" @@@ -467,7 -489,7 +489,7 @@@ orig_head=$branc mb=$(git merge-base "$onto" "$branch") if test "$upstream" = "$onto" && test "$mb" = "$onto" && # linear history? - ! (git rev-list --parents "$onto".."$branch" | grep " .* ") > /dev/null + ! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null then if test -z "$force_rebase" then @@@ -496,7 -518,7 +518,7 @@@ the fi # If the $onto is a proper descendant of the tip of the branch, then -# we just fast forwarded. +# we just fast-forwarded. if test "$mb" = "$branch" then say "Fast-forwarded $branch_name to $onto_name." diff --combined sha1_name.c index ca8f9dba94,d5a240cf07..1739e9e612 --- a/sha1_name.c +++ b/sha1_name.c @@@ -794,6 -794,48 +794,48 @@@ release_return return retval; } + int get_sha1_mb(const char *name, unsigned char *sha1) + { + struct commit *one, *two; + struct commit_list *mbs; + unsigned char sha1_tmp[20]; + const char *dots; + int st; + + dots = strstr(name, "..."); + if (!dots) + return get_sha1(name, sha1); + if (dots == name) + st = get_sha1("HEAD", sha1_tmp); + else { + struct strbuf sb; + strbuf_init(&sb, dots - name); + strbuf_add(&sb, name, dots - name); + st = get_sha1(sb.buf, sha1_tmp); + strbuf_release(&sb); + } + if (st) + return st; + one = lookup_commit_reference_gently(sha1_tmp, 0); + if (!one) + return -1; + + if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) + return -1; + two = lookup_commit_reference_gently(sha1_tmp, 0); + if (!two) + return -1; + mbs = get_merge_bases(one, two, 1); + if (!mbs || mbs->next) + st = -1; + else { + st = 0; + hashcpy(sha1, mbs->item->object.sha1); + } + free_commit_list(mbs); + return st; + } + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" @@@ -804,96 -846,7 +846,96 @@@ int get_sha1(const char *name, unsigne return get_sha1_with_mode(name, sha1, &unused); } -int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode) +/* Must be called only when object_name:filename doesn't exist. */ +static void diagnose_invalid_sha1_path(const char *prefix, + const char *filename, + const unsigned char *tree_sha1, + const char *object_name) +{ + struct stat st; + unsigned char sha1[20]; + unsigned mode; + + if (!prefix) + prefix = ""; + + if (!lstat(filename, &st)) + die("Path '%s' exists on disk, but not in '%s'.", + filename, object_name); + if (errno == ENOENT || errno == ENOTDIR) { + char *fullname = xmalloc(strlen(filename) + + strlen(prefix) + 1); + strcpy(fullname, prefix); + strcat(fullname, filename); + + if (!get_tree_entry(tree_sha1, fullname, + sha1, &mode)) { + die("Path '%s' exists, but not '%s'.\n" + "Did you mean '%s:%s'?", + fullname, + filename, + object_name, + fullname); + } + die("Path '%s' does not exist in '%s'", + filename, object_name); + } +} + +/* Must be called only when :stage:filename doesn't exist. */ +static void diagnose_invalid_index_path(int stage, + const char *prefix, + const char *filename) +{ + struct stat st; + struct cache_entry *ce; + int pos; + unsigned namelen = strlen(filename); + unsigned fullnamelen; + char *fullname; + + if (!prefix) + prefix = ""; + + /* Wrong stage number? */ + pos = cache_name_pos(filename, namelen); + if (pos < 0) + pos = -pos - 1; + ce = active_cache[pos]; + if (ce_namelen(ce) == namelen && + !memcmp(ce->name, filename, namelen)) + die("Path '%s' is in the index, but not at stage %d.\n" + "Did you mean ':%d:%s'?", + filename, stage, + ce_stage(ce), filename); + + /* Confusion between relative and absolute filenames? */ + fullnamelen = namelen + strlen(prefix); + fullname = xmalloc(fullnamelen + 1); + strcpy(fullname, prefix); + strcat(fullname, filename); + pos = cache_name_pos(fullname, fullnamelen); + if (pos < 0) + pos = -pos - 1; + ce = active_cache[pos]; + if (ce_namelen(ce) == fullnamelen && + !memcmp(ce->name, fullname, fullnamelen)) + die("Path '%s' is in the index, but not '%s'.\n" + "Did you mean ':%d:%s'?", + fullname, filename, + ce_stage(ce), fullname); + + if (!lstat(filename, &st)) + die("Path '%s' exists on disk, but not in the index.", filename); + if (errno == ENOENT || errno == ENOTDIR) + die("Path '%s' does not exist (neither on disk nor in the index).", + filename); + + free(fullname); +} + + +int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix) { int ret, bracket_depth; int namelen = strlen(name); @@@ -939,8 -892,6 +981,8 @@@ } pos++; } + if (!gently) + diagnose_invalid_index_path(stage, prefix, cp); return -1; } for (cp = name, bracket_depth = 0; *cp; cp++) { @@@ -953,25 -904,9 +995,25 @@@ } if (*cp == ':') { unsigned char tree_sha1[20]; - if (!get_sha1_1(name, cp-name, tree_sha1)) - return get_tree_entry(tree_sha1, cp+1, sha1, - mode); + char *object_name = NULL; + if (!gently) { + object_name = xmalloc(cp-name+1); + strncpy(object_name, name, cp-name); + object_name[cp-name] = '\0'; + } + if (!get_sha1_1(name, cp-name, tree_sha1)) { + const char *filename = cp+1; + ret = get_tree_entry(tree_sha1, filename, sha1, mode); + if (!gently) { + diagnose_invalid_sha1_path(prefix, filename, + tree_sha1, object_name); + free(object_name); + } + return ret; + } else { + if (!gently) + die("Invalid object name '%s'.", object_name); + } } return ret; }