From bc7127ef0f81d996e9691c6c898b926867aae89f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Sep 2006 02:25:30 -0700 Subject: [PATCH 1/6] ref locking: allow 'foo' when 'foo/bar' used to exist but not anymore. It is normal to have .git/refs/heads/foo directory which is empty after the last branch whose name starts with foo/ is removed. Make sure we notice this case and allow creation of branch foo by removing the empty directory. Signed-off-by: Junio C Hamano --- refs.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/refs.c b/refs.c index 3d4cdd1eb9..b433c0c2d0 100644 --- a/refs.c +++ b/refs.c @@ -473,6 +473,59 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } +static int remove_empty_dir_recursive(char *path, int len) +{ + DIR *dir = opendir(path); + struct dirent *e; + int ret = 0; + + if (!dir) + return -1; + if (path[len-1] != '/') + path[len++] = '/'; + while ((e = readdir(dir)) != NULL) { + struct stat st; + int namlen; + if ((e->d_name[0] == '.') && + ((e->d_name[1] == 0) || + ((e->d_name[1] == '.') && e->d_name[2] == 0))) + continue; /* "." and ".." */ + + namlen = strlen(e->d_name); + if ((len + namlen < PATH_MAX) && + strcpy(path + len, e->d_name) && + !lstat(path, &st) && + S_ISDIR(st.st_mode) && + remove_empty_dir_recursive(path, len + namlen)) + continue; /* happy */ + + /* path too long, stat fails, or non-directory still exists */ + ret = -1; + break; + } + closedir(dir); + if (!ret) { + path[len] = 0; + ret = rmdir(path); + } + return ret; +} + +static int remove_empty_directories(char *file) +{ + /* we want to create a file but there is a directory there; + * if that is an empty directory (or a directory that contains + * only empty directories), remove them. + */ + char path[PATH_MAX]; + int len = strlen(file); + + if (len >= PATH_MAX) /* path too long ;-) */ + return -1; + strcpy(path, file); + return remove_empty_dir_recursive(path, len); +} + static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1) { char *ref_file; @@ -485,6 +538,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock->lock_fd = -1; ref = resolve_ref(ref, lock->old_sha1, mustexist, NULL); + if (!ref && errno == EISDIR) { + /* we are trying to lock foo but we used to + * have foo/bar which now does not exist; + * it is normal for the empty directory 'foo' + * to remain. + */ + ref_file = git_path("%s", orig_ref); + if (remove_empty_directories(ref_file)) + die("there are still refs under '%s'", orig_ref); + ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, NULL); + } if (!ref) { int last_errno = errno; error("unable to resolve reference %s: %s", From 5e290ff75a0e6996f297dc438aceb8e1f29a20a5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Sep 2006 12:37:37 -0700 Subject: [PATCH 2/6] refs: minor restructuring of cached refs data. Once we read packed and loose refs, for_each_ref() and friends kept using them even after write_ref_sha1() and delete_ref() changed the refs. This adds invalidate_cached_refs() as a way to flush the cached information. Signed-off-by: Junio C Hamano --- refs.c | 56 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/refs.c b/refs.c index b433c0c2d0..6ee5f96943 100644 --- a/refs.c +++ b/refs.c @@ -66,12 +66,42 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, return list; } +/* + * Future: need to be in "struct repository" + * when doing a full libification. + */ +struct cached_refs { + char did_loose; + char did_packed; + struct ref_list *loose; + struct ref_list *packed; +} cached_refs; + +static void free_ref_list(struct ref_list *list) +{ + struct ref_list *next; + for ( ; list; list = next) { + next = list->next; + free(list); + } +} + +static void invalidate_cached_refs(void) +{ + struct cached_refs *ca = &cached_refs; + + if (ca->did_loose && ca->loose) + free_ref_list(ca->loose); + if (ca->did_packed && ca->packed) + free_ref_list(ca->packed); + ca->loose = ca->packed = NULL; + ca->did_loose = ca->did_packed = 0; +} + static struct ref_list *get_packed_refs(void) { - static int did_refs = 0; - static struct ref_list *refs = NULL; - - if (!did_refs) { + if (!cached_refs.did_packed) { + struct ref_list *refs = NULL; FILE *f = fopen(git_path("packed-refs"), "r"); if (f) { struct ref_list *list = NULL; @@ -86,9 +116,10 @@ static struct ref_list *get_packed_refs(void) fclose(f); refs = list; } - did_refs = 1; + cached_refs.packed = refs; + cached_refs.did_packed = 1; } - return refs; + return cached_refs.packed; } static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) @@ -138,14 +169,11 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) static struct ref_list *get_loose_refs(void) { - static int did_refs = 0; - static struct ref_list *refs = NULL; - - if (!did_refs) { - refs = get_ref_dir("refs", NULL); - did_refs = 1; + if (!cached_refs.did_loose) { + cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.did_loose = 1; } - return refs; + return cached_refs.loose; } /* We allow "recursive" symbolic refs. Only within reason, though */ @@ -401,6 +429,7 @@ int delete_ref(const char *refname, unsigned char *sha1) fprintf(stderr, "warning: unlink(%s) failed: %s", lock->log_file, strerror(errno)); + invalidate_cached_refs(); return ret; } @@ -665,6 +694,7 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } + invalidate_cached_refs(); if (log_ref_write(lock, sha1, logmsg) < 0) { unlock_ref(lock); return -1; From 5cc3cef997503e7543d927dbe23daca891131168 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Sep 2006 14:14:31 -0700 Subject: [PATCH 3/6] lock_ref_sha1(): do not sometimes error() and sometimes die(). This cleans up the error path in the function so it does not die() itself sometimes while signalling an error with NULL some other times which was inconsistent and confusing. Signed-off-by: Junio C Hamano --- refs.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/refs.c b/refs.c index 6ee5f96943..157de432ce 100644 --- a/refs.c +++ b/refs.c @@ -561,6 +561,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char const char *orig_ref = ref; struct ref_lock *lock; struct stat st; + int last_errno = 0; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); lock = xcalloc(1, sizeof(struct ref_lock)); @@ -574,17 +575,18 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char * to remain. */ ref_file = git_path("%s", orig_ref); - if (remove_empty_directories(ref_file)) - die("there are still refs under '%s'", orig_ref); + if (remove_empty_directories(ref_file)) { + last_errno = errno; + error("there are still refs under '%s'", orig_ref); + goto error_return; + } ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, NULL); } if (!ref) { - int last_errno = errno; + last_errno = errno; error("unable to resolve reference %s: %s", orig_ref, strerror(errno)); - unlock_ref(lock); - errno = last_errno; - return NULL; + goto error_return; } lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -593,11 +595,19 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; - if (safe_create_leading_directories(ref_file)) - die("unable to create directory for %s", ref_file); + if (safe_create_leading_directories(ref_file)) { + last_errno = errno; + error("unable to create directory for %s", ref_file); + goto error_return; + } lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; + + error_return: + unlock_ref(lock); + errno = last_errno; + return NULL; } struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) From 22a3844ebada55c207e2b85fb68509a7c9e2b5f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Sep 2006 14:19:25 -0700 Subject: [PATCH 4/6] lock_ref_sha1(): check D/F conflict with packed ref when creating. This makes the ref locking codepath to notice if an existing ref overlaps with the ref we are creating. Signed-off-by: Junio C Hamano --- refs.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/refs.c b/refs.c index 157de432ce..2bfa92a8a3 100644 --- a/refs.c +++ b/refs.c @@ -588,6 +588,30 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char orig_ref, strerror(errno)); goto error_return; } + if (is_null_sha1(lock->old_sha1)) { + /* The ref did not exist and we are creating it. + * Make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + struct ref_list *list = get_packed_refs(); + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + error("'%s' exists; cannot create '%s'", + list->name, ref); + goto error_return; + } + list = list->next; + } + } + lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(ref); From c0277d15effb9efcaaaa0cd84decf71a327ac07b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Sep 2006 15:02:00 -0700 Subject: [PATCH 5/6] delete_ref(): delete packed ref This implements deletion of a packed ref. Since it is a very rare event to delete a ref compared to looking up, creating and updating, this opts to remove the ref from the packed-ref file instead of doing any of the filesystem based "negative ref" trick to optimize the deletion path. Signed-off-by: Junio C Hamano --- refs.c | 109 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/refs.c b/refs.c index 2bfa92a8a3..858c53445c 100644 --- a/refs.c +++ b/refs.c @@ -406,33 +406,6 @@ int get_ref_sha1(const char *ref, unsigned char *sha1) return read_ref(mkpath("refs/%s", ref), sha1); } -int delete_ref(const char *refname, unsigned char *sha1) -{ - struct ref_lock *lock; - int err, i, ret = 0; - - lock = lock_any_ref_for_update(refname, sha1); - if (!lock) - return 1; - i = strlen(lock->lk->filename) - 5; /* .lock */ - lock->lk->filename[i] = 0; - err = unlink(lock->lk->filename); - if (err) { - ret = 1; - error("unlink(%s) failed: %s", - lock->lk->filename, strerror(errno)); - } - lock->lk->filename[i] = '.'; - - err = unlink(lock->log_file); - if (err && errno != ENOENT) - fprintf(stderr, "warning: unlink(%s) failed: %s", - lock->log_file, strerror(errno)); - - invalidate_cached_refs(); - return ret; -} - /* * Make sure "ref" is something reasonable to have under ".git/refs/"; * We do not like it if: @@ -555,7 +528,7 @@ static int remove_empty_directories(char *file) return remove_empty_dir_recursive(path, len); } -static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1) +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) { char *ref_file; const char *orig_ref = ref; @@ -567,7 +540,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - ref = resolve_ref(ref, lock->old_sha1, mustexist, NULL); + ref = resolve_ref(ref, lock->old_sha1, mustexist, flag); if (!ref && errno == EISDIR) { /* we are trying to lock foo but we used to * have foo/bar which now does not exist; @@ -580,7 +553,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char error("there are still refs under '%s'", orig_ref); goto error_return; } - ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, NULL); + ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag); } if (!ref) { last_errno = errno; @@ -640,12 +613,84 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) if (check_ref_format(ref)) return NULL; strcpy(refpath, mkpath("refs/%s", ref)); - return lock_ref_sha1_basic(refpath, old_sha1); + return lock_ref_sha1_basic(refpath, old_sha1, NULL); } struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) { - return lock_ref_sha1_basic(ref, old_sha1); + return lock_ref_sha1_basic(ref, old_sha1, NULL); +} + +static int repack_without_ref(const char *refname) +{ + struct ref_list *list, *packed_ref_list; + int fd; + int found = 0; + struct lock_file packlock; + + packed_ref_list = get_packed_refs(); + for (list = packed_ref_list; list; list = list->next) { + if (!strcmp(refname, list->name)) { + found = 1; + break; + } + } + if (!found) + return 0; + memset(&packlock, 0, sizeof(packlock)); + fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); + if (fd < 0) + return error("cannot delete '%s' from packed refs", refname); + + for (list = packed_ref_list; list; list = list->next) { + char line[PATH_MAX + 100]; + int len; + + if (!strcmp(refname, list->name)) + continue; + len = snprintf(line, sizeof(line), "%s %s\n", + sha1_to_hex(list->sha1), list->name); + /* this should not happen but just being defensive */ + if (len > sizeof(line)) + die("too long a refname '%s'", list->name); + write_or_die(fd, line, len); + } + return commit_lock_file(&packlock); +} + +int delete_ref(const char *refname, unsigned char *sha1) +{ + struct ref_lock *lock; + int err, i, ret = 0, flag = 0; + + lock = lock_ref_sha1_basic(refname, sha1, &flag); + if (!lock) + return 1; + if (!(flag & REF_ISPACKED)) { + /* loose */ + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + err = unlink(lock->lk->filename); + if (err) { + ret = 1; + error("unlink(%s) failed: %s", + lock->lk->filename, strerror(errno)); + } + lock->lk->filename[i] = '.'; + } + /* removing the loose one could have resurrected an earlier + * packed one. Also, if it was not loose we need to repack + * without it. + */ + ret |= repack_without_ref(refname); + + err = unlink(lock->log_file); + if (err && errno != ENOENT) + fprintf(stderr, "warning: unlink(%s) failed: %s", + lock->log_file, strerror(errno)); + invalidate_cached_refs(); + unlock_ref(lock); + return ret; } void unlock_ref(struct ref_lock *lock) From 936a9508cc0d5369b00c76ee63cdb81556e7be39 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Sep 2006 15:21:28 -0700 Subject: [PATCH 6/6] git-branch: remove D/F check done by hand. Now ref creation codepath in lock_ref_sha1() and friends notices the directory/file conflict situation, we do not do this by hand in git-branch anymore. Signed-off-by: Junio C Hamano --- git-branch.sh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/git-branch.sh b/git-branch.sh index c61683033d..bf84b30695 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -121,16 +121,6 @@ then done fi -branchdir=$(dirname $branchname) -while test "$branchdir" != "." -do - if git-show-ref --verify --quiet -- "refs/heads/$branchdir" - then - die "$branchdir already exists." - fi - branchdir=$(dirname $branchdir) -done - prev='' if git-show-ref --verify --quiet -- "refs/heads/$branchname" then