diff options
-rw-r--r-- | builtin-branch.c | 55 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rwxr-xr-x | git-checkout.sh | 74 | ||||
-rw-r--r-- | path.c | 26 | ||||
-rw-r--r-- | setup.c | 5 | ||||
-rwxr-xr-x | t/t3200-branch.sh | 2 | ||||
-rw-r--r-- | wt-status.c | 13 |
7 files changed, 135 insertions, 42 deletions
diff --git a/builtin-branch.c b/builtin-branch.c index d3df5a57f1..c760e188ea 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -275,7 +275,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } } -static void print_ref_list(int kinds, int verbose, int abbrev) +static void print_ref_list(int kinds, int detached, int verbose, int abbrev) { int i; struct ref_list ref_list; @@ -286,8 +286,20 @@ static void print_ref_list(int kinds, int verbose, int abbrev) qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); + detached = (detached && (kinds & REF_LOCAL_BRANCH)); + if (detached) { + struct ref_item item; + item.name = "(no branch)"; + item.kind = REF_LOCAL_BRANCH; + hashcpy(item.sha1, head_sha1); + if (strlen(item.name) > ref_list.maxwidth) + ref_list.maxwidth = strlen(item.name); + print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1); + } + for (i = 0; i < ref_list.index; i++) { - int current = (ref_list.list[i].kind == REF_LOCAL_BRANCH) && + int current = !detached && + (ref_list.list[i].kind == REF_LOCAL_BRANCH) && !strcmp(ref_list.list[i].name, head); print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, abbrev, current); @@ -296,7 +308,8 @@ static void print_ref_list(int kinds, int verbose, int abbrev) free_ref_list(&ref_list); } -static void create_branch(const char *name, const char *start, +static void create_branch(const char *name, const char *start_name, + unsigned char *start_sha1, int force, int reflog) { struct ref_lock *lock; @@ -315,9 +328,14 @@ static void create_branch(const char *name, const char *start, die("Cannot force update the current branch."); } - if (get_sha1(start, sha1) || - (commit = lookup_commit_reference(sha1)) == NULL) - die("Not a valid branch point: '%s'.", start); + if (start_sha1) + /* detached HEAD */ + hashcpy(sha1, start_sha1); + else if (get_sha1(start_name, sha1)) + die("Not a valid object name: '%s'.", start_name); + + 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); @@ -326,7 +344,8 @@ static void create_branch(const char *name, const char *start, if (reflog) { log_all_ref_updates = 1; - snprintf(msg, sizeof msg, "branch: Created from %s", start); + snprintf(msg, sizeof msg, "branch: Created from %s", + start_name); } if (write_ref_sha1(lock, sha1, msg) < 0) @@ -338,6 +357,9 @@ static void rename_branch(const char *oldname, const char *newname, int force) char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100]; unsigned char sha1[20]; + if (!oldname) + die("cannot rename the curren branch while not on any."); + if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) die("Old branchname too long"); @@ -367,7 +389,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, force_delete = 0, force_create = 0; int rename = 0, force_rename = 0; - int verbose = 0, abbrev = DEFAULT_ABBREV; + int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; int reflog = 0; int kinds = REF_LOCAL_BRANCH; int i; @@ -444,22 +466,27 @@ int cmd_branch(int argc, const char **argv, const char *prefix) head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); if (!head) die("Failed to resolve HEAD as a valid ref."); - if (strncmp(head, "refs/heads/", 11)) - die("HEAD not found below refs/heads!"); - head += 11; + if (!strcmp(head, "HEAD")) { + detached = 1; + } + else { + if (strncmp(head, "refs/heads/", 11)) + die("HEAD not found below refs/heads!"); + head += 11; + } if (delete) return delete_branches(argc - i, argv + i, force_delete, kinds); else if (i == argc) - print_ref_list(kinds, verbose, abbrev); + print_ref_list(kinds, detached, verbose, abbrev); else if (rename && (i == argc - 1)) rename_branch(head, argv[i], force_rename); else if (rename && (i == argc - 2)) rename_branch(argv[i], argv[i + 1], force_rename); else if (i == argc - 1) - create_branch(argv[i], head, force_create, reflog); + create_branch(argv[i], head, head_sha1, force_create, reflog); else if (i == argc - 2) - create_branch(argv[i], argv[i + 1], force_create, reflog); + create_branch(argv[i], argv[i+1], NULL, force_create, reflog); else usage(builtin_branch_usage); @@ -299,7 +299,7 @@ extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); extern int create_symref(const char *ref, const char *refs_heads_master); -extern int validate_symref(const char *ref); +extern int validate_headref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); diff --git a/git-checkout.sh b/git-checkout.sh index 92ec069a3a..8f4356d49a 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -6,6 +6,7 @@ SUBDIRECTORY_OK=Sometimes old_name=HEAD old=$(git-rev-parse --verify $old_name 2>/dev/null) +oldbranch=$(git-symbolic-ref $old_name 2>/dev/null) new= new_name= force= @@ -13,6 +14,8 @@ branch= newbranch= newbranch_log= merge= +LF=' +' while [ "$#" != "0" ]; do arg="$1" shift @@ -50,7 +53,7 @@ while [ "$#" != "0" ]; do exit 1 fi new="$rev" - new_name="$arg^0" + new_name="$arg" if git-show-ref --verify --quiet -- "refs/heads/$arg" then branch="$arg" @@ -139,23 +142,49 @@ fi [ -z "$new" ] && new=$old && new_name="$old_name" -# If we don't have an old branch that we're switching to, +# If we don't have an existing branch that we're switching to, # and we don't have a new branch name for the target we -# are switching to, then we'd better just be checking out -# what we already had +# are switching to, then we are detaching our HEAD from any +# branch. However, if "git checkout HEAD" detaches the HEAD +# from the current branch, even though that may be logically +# correct, it feels somewhat funny. More importantly, we do not +# want "git checkout" nor "git checkout -f" to detach HEAD. -[ -z "$branch$newbranch" ] && - [ "$new" != "$old" ] && - die "git checkout: provided reference cannot be checked out directly +detached= +detach_warn= - You need -b to associate a new branch with the wanted checkout. Example: - git checkout -b <new_branch_name> $arg -" +if test -z "$branch$newbranch" && test "$new" != "$old" +then + detached="$new" + if test -n "$oldbranch" + then + detach_warn="warning: you are not on ANY branch anymore. +If you meant to create a new branch from the commit, you need -b to +associate a new branch with the wanted checkout. Example: + git checkout -b <new_branch_name> $arg" + fi +elif test -z "$oldbranch" && test -n "$branch" +then + # Coming back... + if test -z "$force" + then + git show-ref -d -s | grep "$old" >/dev/null || { + echo >&2 \ +"You are not on any branch and switching to branch '$new_name' +may lose your changes. At this point, you can do one of two things: + (1) Decide it is Ok and say 'git checkout -f $new_name'; + (2) Start a new branch from the current commit, by saying + 'git checkout -b <branch-name>'. +Leaving your HEAD detached; not switching to branch '$new_name'." + exit 1; + } + fi +fi if [ "X$old" = X ] then - echo "warning: You do not appear to currently be on a branch." >&2 - echo "warning: Forcing checkout of $new_name." >&2 + echo >&2 "warning: You appear to be on a branch yet to be born." + echo >&2 "warning: Forcing checkout of $new_name." force=1 fi @@ -226,8 +255,25 @@ if [ "$?" -eq 0 ]; then git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit branch="$newbranch" fi - [ "$branch" ] && - GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" + if test -n "$branch" + then + GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" + elif test -n "$detached" + then + # NEEDSWORK: we would want a command to detach the HEAD + # atomically, instead of this handcrafted command sequence. + # Perhaps: + # git update-ref --detach HEAD $new + # or something like that... + # + echo "$detached" >"$GIT_DIR/HEAD.new" && + mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" || + die "Cannot detach HEAD" + if test -n "$detach_warn" + then + echo >&2 "$detach_warn" + fi + fi rm -f "$GIT_DIR/MERGE_HEAD" else exit 1 @@ -90,10 +90,11 @@ int git_mkstemp(char *path, size_t len, const char *template) } -int validate_symref(const char *path) +int validate_headref(const char *path) { struct stat st; char *buf, buffer[256]; + unsigned char sha1[20]; int len, fd; if (lstat(path, &st) < 0) @@ -119,14 +120,23 @@ int validate_symref(const char *path) /* * Is it a symbolic ref? */ - if (len < 4 || memcmp("ref:", buffer, 4)) + if (len < 4) return -1; - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - if (len >= 5 && !memcmp("refs/", buf, 5)) + if (!memcmp("ref:", buffer, 4)) { + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + if (len >= 5 && !memcmp("refs/", buf, 5)) + return 0; + } + + /* + * Is this a detached HEAD? + */ + if (!get_sha1_hex(buffer, sha1)) return 0; + return -1; } @@ -241,7 +251,7 @@ char *enter_repo(char *path, int strict) return NULL; if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && - validate_symref("HEAD") == 0) { + validate_headref("HEAD") == 0) { putenv("GIT_DIR=."); check_repository_format(); return path; @@ -138,7 +138,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec) * GIT_OBJECT_DIRECTORY environment variable * - a refs/ directory * - either a HEAD symlink or a HEAD file that is formatted as - * a proper "ref:". + * a proper "ref:", or a regular file HEAD that has a properly + * formatted sha1 object name. */ static int is_git_directory(const char *suspect) { @@ -161,7 +162,7 @@ static int is_git_directory(const char *suspect) return 0; strcpy(path + len, "/HEAD"); - if (validate_symref(path)) + if (validate_headref(path)) return 0; return 1; diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index a6ea0f6a19..bb80e4286a 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -48,7 +48,7 @@ test_expect_success \ test ! -f .git/logs/refs/heads/d/e/f' cat >expect <<EOF -0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 checkout: Created from master^0 +0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 checkout: Created from master EOF test_expect_success \ 'git checkout -b g/h/i -l should create a branch and a log' \ diff --git a/wt-status.c b/wt-status.c index a849951774..daba9a6105 100644 --- a/wt-status.c +++ b/wt-status.c @@ -302,9 +302,18 @@ void wt_status_print(struct wt_status *s) unsigned char sha1[20]; s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; - if (s->branch) + if (s->branch) { + const char *on_what = "On branch "; + const char *branch_name = s->branch; + if (!strncmp(branch_name, "refs/heads/", 11)) + branch_name += 11; + else if (!strcmp(branch_name, "HEAD")) { + branch_name = ""; + on_what = "Not currently on any branch."; + } color_printf_ln(color(WT_STATUS_HEADER), - "# On branch %s", s->branch); + "# %s%s", on_what, branch_name); + } if (s->is_initial) { color_printf_ln(color(WT_STATUS_HEADER), "#"); |