Skip to content

Commit c847f53

Browse files
author
Junio C Hamano
committed
Detached HEAD (experimental)
This allows "git checkout v1.4.3" to dissociate the HEAD of repository from any branch. After this point, "git branch" starts reporting that you are not on any branch. You can go back to an existing branch by saying "git checkout master", for example. This is still experimental. While I think it makes sense to allow commits on top of detached HEAD, it is rather dangerous unless you are careful in the current form. Next "git checkout master" will obviously lose what you have done, so we might want to require "git checkout -f" out of a detached HEAD if we find that the HEAD commit is not an ancestor of any other branches. There is no such safety valve implemented right now. On the other hand, the reason the user did not start the ad-hoc work on a new branch with "git checkout -b" was probably because the work was of a throw-away nature, so the convenience of not having that safety valve might be even better. The user, after accumulating some commits on top of a detached HEAD, can always create a new branch with "git checkout -b" not to lose useful work done while the HEAD was detached. We'll see. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0016a48 commit c847f53

File tree

5 files changed

+50
-23
lines changed

5 files changed

+50
-23
lines changed

builtin-branch.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)
308308
free_ref_list(&ref_list);
309309
}
310310

311-
static void create_branch(const char *name, const char *start,
311+
static void create_branch(const char *name, const char *start_name,
312+
unsigned char *start_sha1,
312313
int force, int reflog)
313314
{
314315
struct ref_lock *lock;
@@ -327,9 +328,14 @@ static void create_branch(const char *name, const char *start,
327328
die("Cannot force update the current branch.");
328329
}
329330

330-
if (get_sha1(start, sha1) ||
331-
(commit = lookup_commit_reference(sha1)) == NULL)
332-
die("Not a valid branch point: '%s'.", start);
331+
if (start_sha1)
332+
/* detached HEAD */
333+
hashcpy(sha1, start_sha1);
334+
else if (get_sha1(start_name, sha1))
335+
die("Not a valid object name: '%s'.", start_name);
336+
337+
if ((commit = lookup_commit_reference(sha1)) == NULL)
338+
die("Not a valid branch point: '%s'.", start_name);
333339
hashcpy(sha1, commit->object.sha1);
334340

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

339345
if (reflog) {
340346
log_all_ref_updates = 1;
341-
snprintf(msg, sizeof msg, "branch: Created from %s", start);
347+
snprintf(msg, sizeof msg, "branch: Created from %s",
348+
start_name);
342349
}
343350

344351
if (write_ref_sha1(lock, sha1, msg) < 0)
@@ -350,6 +357,9 @@ static void rename_branch(const char *oldname, const char *newname, int force)
350357
char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
351358
unsigned char sha1[20];
352359

360+
if (!oldname)
361+
die("cannot rename the curren branch while not on any.");
362+
353363
if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref))
354364
die("Old branchname too long");
355365

@@ -474,9 +484,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
474484
else if (rename && (i == argc - 2))
475485
rename_branch(argv[i], argv[i + 1], force_rename);
476486
else if (i == argc - 1)
477-
create_branch(argv[i], head, force_create, reflog);
487+
create_branch(argv[i], head, head_sha1, force_create, reflog);
478488
else if (i == argc - 2)
479-
create_branch(argv[i], argv[i + 1], force_create, reflog);
489+
create_branch(argv[i], argv[i+1], NULL, force_create, reflog);
480490
else
481491
usage(builtin_branch_usage);
482492

cache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
299299
extern int read_ref(const char *filename, unsigned char *sha1);
300300
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
301301
extern int create_symref(const char *ref, const char *refs_heads_master);
302-
extern int validate_symref(const char *ref);
302+
extern int validate_headref(const char *ref);
303303

304304
extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
305305
extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);

git-checkout.sh

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,19 @@ fi
144144
# are switching to, then we'd better just be checking out
145145
# what we already had
146146

147-
[ -z "$branch$newbranch" ] &&
148-
[ "$new" != "$old" ] &&
149-
die "git checkout: provided reference cannot be checked out directly
150-
151-
You need -b to associate a new branch with the wanted checkout. Example:
147+
if test -z "$branch$newbranch" && test "$new" != "$old"
148+
then
149+
# NEEDSWORK: we would want to have this command here
150+
# that allows us to detach the HEAD atomically.
151+
# git update-ref --detach HEAD "$new"
152+
rm -f "$GIT_DIR/HEAD"
153+
echo "$new" >"$GIT_DIR/HEAD"
154+
echo >&2 "WARNING: you are not on ANY branch anymore.
155+
If you meant to create a new branch from the commit, you need -b to
156+
associate a new branch with the wanted checkout. Example:
152157
git checkout -b <new_branch_name> $arg
153158
"
159+
fi
154160

155161
if [ "X$old" = X ]
156162
then

path.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,11 @@ int git_mkstemp(char *path, size_t len, const char *template)
9090
}
9191

9292

93-
int validate_symref(const char *path)
93+
int validate_headref(const char *path)
9494
{
9595
struct stat st;
9696
char *buf, buffer[256];
97+
unsigned char sha1[20];
9798
int len, fd;
9899

99100
if (lstat(path, &st) < 0)
@@ -119,14 +120,23 @@ int validate_symref(const char *path)
119120
/*
120121
* Is it a symbolic ref?
121122
*/
122-
if (len < 4 || memcmp("ref:", buffer, 4))
123+
if (len < 4)
123124
return -1;
124-
buf = buffer + 4;
125-
len -= 4;
126-
while (len && isspace(*buf))
127-
buf++, len--;
128-
if (len >= 5 && !memcmp("refs/", buf, 5))
125+
if (!memcmp("ref:", buffer, 4)) {
126+
buf = buffer + 4;
127+
len -= 4;
128+
while (len && isspace(*buf))
129+
buf++, len--;
130+
if (len >= 5 && !memcmp("refs/", buf, 5))
131+
return 0;
132+
}
133+
134+
/*
135+
* Is this a detached HEAD?
136+
*/
137+
if (!get_sha1_hex(buffer, sha1))
129138
return 0;
139+
130140
return -1;
131141
}
132142

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

243253
if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
244-
validate_symref("HEAD") == 0) {
254+
validate_headref("HEAD") == 0) {
245255
putenv("GIT_DIR=.");
246256
check_repository_format();
247257
return path;

setup.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
138138
* GIT_OBJECT_DIRECTORY environment variable
139139
* - a refs/ directory
140140
* - either a HEAD symlink or a HEAD file that is formatted as
141-
* a proper "ref:".
141+
* a proper "ref:", or a regular file HEAD that has a properly
142+
* formatted sha1 object name.
142143
*/
143144
static int is_git_directory(const char *suspect)
144145
{
@@ -161,7 +162,7 @@ static int is_git_directory(const char *suspect)
161162
return 0;
162163

163164
strcpy(path + len, "/HEAD");
164-
if (validate_symref(path))
165+
if (validate_headref(path))
165166
return 0;
166167

167168
return 1;

0 commit comments

Comments
 (0)