Merge branch 'jc/detached-head'

* jc/detached-head:
  git-checkout: handle local changes sanely when detaching HEAD
  git-checkout: safety check for detached HEAD checks existing refs
  git-checkout: fix branch name output from the command
  git-checkout: safety when coming back from the detached HEAD state.
  git-checkout: rewording comments regarding detached HEAD.
  git-checkout: do not warn detaching HEAD when it is already detached.
  Detached HEAD (experimental)
  git-branch: show detached HEAD
  git-status: show detached HEAD
maint
Junio C Hamano 2007-01-11 16:47:34 -08:00
commit c388761c15
7 changed files with 135 additions and 42 deletions

View File

@ -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; int i;
struct ref_list ref_list; 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); 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++) { 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); !strcmp(ref_list.list[i].name, head);
print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
abbrev, current); abbrev, current);
@ -296,7 +308,8 @@ static void print_ref_list(int kinds, int verbose, int abbrev)
free_ref_list(&ref_list); 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) int force, int reflog)
{ {
struct ref_lock *lock; 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."); die("Cannot force update the current branch.");
} }


if (get_sha1(start, sha1) || if (start_sha1)
(commit = lookup_commit_reference(sha1)) == NULL) /* detached HEAD */
die("Not a valid branch point: '%s'.", start); 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); hashcpy(sha1, commit->object.sha1);


lock = lock_any_ref_for_update(ref, NULL); lock = lock_any_ref_for_update(ref, NULL);
@ -326,7 +344,8 @@ static void create_branch(const char *name, const char *start,


if (reflog) { if (reflog) {
log_all_ref_updates = 1; 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) 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]; char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
unsigned char sha1[20]; 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)) if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref))
die("Old branchname too long"); 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 delete = 0, force_delete = 0, force_create = 0;
int rename = 0, force_rename = 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 reflog = 0;
int kinds = REF_LOCAL_BRANCH; int kinds = REF_LOCAL_BRANCH;
int i; 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)); head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
if (!head) if (!head)
die("Failed to resolve HEAD as a valid ref."); die("Failed to resolve HEAD as a valid ref.");
if (!strcmp(head, "HEAD")) {
detached = 1;
}
else {
if (strncmp(head, "refs/heads/", 11)) if (strncmp(head, "refs/heads/", 11))
die("HEAD not found below refs/heads!"); die("HEAD not found below refs/heads!");
head += 11; head += 11;
}


if (delete) if (delete)
return delete_branches(argc - i, argv + i, force_delete, kinds); return delete_branches(argc - i, argv + i, force_delete, kinds);
else if (i == argc) else if (i == argc)
print_ref_list(kinds, verbose, abbrev); print_ref_list(kinds, detached, verbose, abbrev);
else if (rename && (i == argc - 1)) else if (rename && (i == argc - 1))
rename_branch(head, argv[i], force_rename); rename_branch(head, argv[i], force_rename);
else if (rename && (i == argc - 2)) else if (rename && (i == argc - 2))
rename_branch(argv[i], argv[i + 1], force_rename); rename_branch(argv[i], argv[i + 1], force_rename);
else if (i == argc - 1) 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) 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 else
usage(builtin_branch_usage); usage(builtin_branch_usage);



View File

@ -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 int read_ref(const char *filename, unsigned char *sha1);
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); 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 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 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); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);

View File

@ -6,6 +6,7 @@ SUBDIRECTORY_OK=Sometimes


old_name=HEAD old_name=HEAD
old=$(git-rev-parse --verify $old_name 2>/dev/null) old=$(git-rev-parse --verify $old_name 2>/dev/null)
oldbranch=$(git-symbolic-ref $old_name 2>/dev/null)
new= new=
new_name= new_name=
force= force=
@ -13,6 +14,8 @@ branch=
newbranch= newbranch=
newbranch_log= newbranch_log=
merge= merge=
LF='
'
while [ "$#" != "0" ]; do while [ "$#" != "0" ]; do
arg="$1" arg="$1"
shift shift
@ -50,7 +53,7 @@ while [ "$#" != "0" ]; do
exit 1 exit 1
fi fi
new="$rev" new="$rev"
new_name="$arg^0" new_name="$arg"
if git-show-ref --verify --quiet -- "refs/heads/$arg" if git-show-ref --verify --quiet -- "refs/heads/$arg"
then then
branch="$arg" branch="$arg"
@ -139,23 +142,49 @@ fi


[ -z "$new" ] && new=$old && new_name="$old_name" [ -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 # and we don't have a new branch name for the target we
# are switching to, then we'd better just be checking out # are switching to, then we are detaching our HEAD from any
# what we already had # 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" ] && detached=
[ "$new" != "$old" ] && detach_warn=
die "git checkout: provided reference cannot be checked out directly


You need -b to associate a new branch with the wanted checkout. Example: if test -z "$branch$newbranch" && test "$new" != "$old"
git checkout -b <new_branch_name> $arg 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 ] if [ "X$old" = X ]
then then
echo "warning: You do not appear to currently be on a branch." >&2 echo >&2 "warning: You appear to be on a branch yet to be born."
echo "warning: Forcing checkout of $new_name." >&2 echo >&2 "warning: Forcing checkout of $new_name."
force=1 force=1
fi fi


@ -226,8 +255,25 @@ if [ "$?" -eq 0 ]; then
git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
branch="$newbranch" branch="$newbranch"
fi fi
[ "$branch" ] && if test -n "$branch"
then
GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" 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" rm -f "$GIT_DIR/MERGE_HEAD"
else else
exit 1 exit 1

16
path.c
View File

@ -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; struct stat st;
char *buf, buffer[256]; char *buf, buffer[256];
unsigned char sha1[20];
int len, fd; int len, fd;


if (lstat(path, &st) < 0) if (lstat(path, &st) < 0)
@ -119,14 +120,23 @@ int validate_symref(const char *path)
/* /*
* Is it a symbolic ref? * Is it a symbolic ref?
*/ */
if (len < 4 || memcmp("ref:", buffer, 4)) if (len < 4)
return -1; return -1;
if (!memcmp("ref:", buffer, 4)) {
buf = buffer + 4; buf = buffer + 4;
len -= 4; len -= 4;
while (len && isspace(*buf)) while (len && isspace(*buf))
buf++, len--; buf++, len--;
if (len >= 5 && !memcmp("refs/", buf, 5)) if (len >= 5 && !memcmp("refs/", buf, 5))
return 0; return 0;
}

/*
* Is this a detached HEAD?
*/
if (!get_sha1_hex(buffer, sha1))
return 0;

return -1; return -1;
} }


@ -241,7 +251,7 @@ char *enter_repo(char *path, int strict)
return NULL; return NULL;


if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
validate_symref("HEAD") == 0) { validate_headref("HEAD") == 0) {
putenv("GIT_DIR=."); putenv("GIT_DIR=.");
check_repository_format(); check_repository_format();
return path; return path;

View File

@ -138,7 +138,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
* GIT_OBJECT_DIRECTORY environment variable * GIT_OBJECT_DIRECTORY environment variable
* - a refs/ directory * - a refs/ directory
* - either a HEAD symlink or a HEAD file that is formatted as * - 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) static int is_git_directory(const char *suspect)
{ {
@ -161,7 +162,7 @@ static int is_git_directory(const char *suspect)
return 0; return 0;


strcpy(path + len, "/HEAD"); strcpy(path + len, "/HEAD");
if (validate_symref(path)) if (validate_headref(path))
return 0; return 0;


return 1; return 1;

View File

@ -48,7 +48,7 @@ test_expect_success \
test ! -f .git/logs/refs/heads/d/e/f' test ! -f .git/logs/refs/heads/d/e/f'


cat >expect <<EOF 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 EOF
test_expect_success \ test_expect_success \
'git checkout -b g/h/i -l should create a branch and a log' \ 'git checkout -b g/h/i -l should create a branch and a log' \

View File

@ -302,9 +302,18 @@ void wt_status_print(struct wt_status *s)
unsigned char sha1[20]; unsigned char sha1[20];
s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; 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), color_printf_ln(color(WT_STATUS_HEADER),
"# On branch %s", s->branch); "# %s%s", on_what, branch_name);
}


if (s->is_initial) { if (s->is_initial) {
color_printf_ln(color(WT_STATUS_HEADER), "#"); color_printf_ln(color(WT_STATUS_HEADER), "#");