diff options
Diffstat (limited to 'refs.c')
-rw-r--r-- | refs.c | 602 |
1 files changed, 485 insertions, 117 deletions
@@ -3,15 +3,193 @@ #include <errno.h> +struct ref_list { + struct ref_list *next; + unsigned char flag; /* ISSYMREF? ISPACKED? */ + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +static const char *parse_ref_line(char *line, unsigned char *sha1) +{ + /* + * 42: the answer to everything. + * + * In this case, it happens to be the answer to + * 40 (length of sha1 hex representation) + * +1 (space in between hex and name) + * +1 (newline at the end of the line) + */ + int len = strlen(line) - 42; + + if (len <= 0) + return NULL; + if (get_sha1_hex(line, sha1) < 0) + return NULL; + if (!isspace(line[40])) + return NULL; + line += 41; + if (isspace(*line)) + return NULL; + if (line[len] != '\n') + return NULL; + line[len] = 0; + return line; +} + +static struct ref_list *add_ref(const char *name, const unsigned char *sha1, + int flag, struct ref_list *list) +{ + int len; + struct ref_list **p = &list, *entry; + + /* Find the place to insert the ref into.. */ + while ((entry = *p) != NULL) { + int cmp = strcmp(entry->name, name); + if (cmp > 0) + break; + + /* Same as existing entry? */ + if (!cmp) + return list; + p = &entry->next; + } + + /* Allocate it and add it in.. */ + len = strlen(name) + 1; + entry = xmalloc(sizeof(struct ref_list) + len); + hashcpy(entry->sha1, sha1); + memcpy(entry->name, name, len); + entry->flag = flag; + entry->next = *p; + *p = entry; + 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) +{ + 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; + char refline[PATH_MAX]; + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name = parse_ref_line(refline, sha1); + if (!name) + continue; + list = add_ref(name, sha1, REF_ISPACKED, list); + } + fclose(f); + refs = list; + } + cached_refs.packed = refs; + cached_refs.did_packed = 1; + } + return cached_refs.packed; +} + +static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) +{ + DIR *dir = opendir(git_path("%s", base)); + + if (dir) { + struct dirent *de; + int baselen = strlen(base); + char *ref = xmalloc(baselen + 257); + + memcpy(ref, base, baselen); + if (baselen && base[baselen-1] != '/') + ref[baselen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + unsigned char sha1[20]; + struct stat st; + int flag; + int namelen; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(ref + baselen, de->d_name, namelen+1); + if (stat(git_path("%s", ref), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + list = get_ref_dir(ref, list); + continue; + } + if (!resolve_ref(ref, sha1, 1, &flag)) { + error("%s points nowhere!", ref); + continue; + } + list = add_ref(ref, sha1, flag, list); + } + free(ref); + closedir(dir); + } + return list; +} + +static struct ref_list *get_loose_refs(void) +{ + if (!cached_refs.did_loose) { + cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.did_loose = 1; + } + return cached_refs.loose; +} + /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -const char *resolve_ref(const char *path, unsigned char *sha1, int reading) +const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH, len; char buffer[256]; + static char ref_buffer[256]; + + if (flag) + *flag = 0; for (;;) { + const char *path = git_path("%s", ref); struct stat st; char *buf; int fd; @@ -27,17 +205,31 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) * reading. */ if (lstat(path, &st) < 0) { + struct ref_list *list = get_packed_refs(); + while (list) { + if (!strcmp(ref, list->name)) { + hashcpy(sha1, list->sha1); + if (flag) + *flag |= REF_ISPACKED; + return ref; + } + list = list->next; + } if (reading || errno != ENOENT) return NULL; hashclr(sha1); - return path; + return ref; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { len = readlink(path, buffer, sizeof(buffer)-1); if (len >= 5 && !memcmp("refs/", buffer, 5)) { - path = git_path("%.*s", len, buffer); + buffer[len] = 0; + strcpy(ref_buffer, buffer); + ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; continue; } } @@ -68,19 +260,24 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) while (len && isspace(*buf)) buf++, len--; while (len && isspace(buf[len-1])) - buf[--len] = 0; - path = git_path("%.*s", len, buf); + len--; + buf[len] = 0; + memcpy(ref_buffer, buf, len + 1); + ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; } if (len < 40 || get_sha1_hex(buffer, sha1)) return NULL; - return path; + return ref; } -int create_symref(const char *git_HEAD, const char *refs_heads_master) +int create_symref(const char *ref_target, const char *refs_heads_master) { const char *lockpath; char ref[1000]; int fd, len, written; + const char *git_HEAD = git_path("%s", ref_target); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { @@ -118,104 +315,101 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master) return 0; } -int read_ref(const char *filename, unsigned char *sha1) +int read_ref(const char *ref, unsigned char *sha1) { - if (resolve_ref(filename, sha1, 1)) + if (resolve_ref(ref, sha1, 1, NULL)) return 0; return -1; } -static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim) +static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, + void *cb_data) { - int retval = 0; - DIR *dir = opendir(git_path("%s", base)); - - if (dir) { - struct dirent *de; - int baselen = strlen(base); - char *path = xmalloc(baselen + 257); - - if (!strncmp(base, "./", 2)) { - base += 2; - baselen -= 2; + int retval; + struct ref_list *packed = get_packed_refs(); + struct ref_list *loose = get_loose_refs(); + + while (packed && loose) { + struct ref_list *entry; + int cmp = strcmp(packed->name, loose->name); + if (!cmp) { + packed = packed->next; + continue; } - memcpy(path, base, baselen); - if (baselen && base[baselen-1] != '/') - path[baselen++] = '/'; - - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - struct stat st; - int namelen; + if (cmp > 0) { + entry = loose; + loose = loose->next; + } else { + entry = packed; + packed = packed->next; + } + if (strncmp(base, entry->name, trim)) + continue; + if (is_null_sha1(entry->sha1)) + continue; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + continue; + } + retval = fn(entry->name + trim, entry->sha1, + entry->flag, cb_data); + if (retval) + return retval; + } - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if (namelen > 255) - continue; - if (has_extension(de->d_name, ".lock")) - continue; - memcpy(path + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", path), &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - retval = do_for_each_ref(path, fn, trim); - if (retval) - break; - continue; - } - if (read_ref(git_path("%s", path), sha1) < 0) { - error("%s points nowhere!", path); - continue; - } - if (!has_sha1_file(sha1)) { - error("%s does not point to a valid " - "commit object!", path); - continue; - } - retval = fn(path + trim, sha1); + packed = packed ? packed : loose; + while (packed) { + if (!strncmp(base, packed->name, trim)) { + retval = fn(packed->name + trim, packed->sha1, + packed->flag, cb_data); if (retval) - break; + return retval; } - free(path); - closedir(dir); + packed = packed->next; } - return retval; + return 0; } -int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int head_ref(each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; - if (!read_ref(git_path("HEAD"), sha1)) - return fn("HEAD", sha1); + int flag; + + if (resolve_ref("HEAD", sha1, 1, &flag)) + return fn("HEAD", sha1, flag, cb_data); return 0; } -int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs", fn, 0); + return do_for_each_ref("refs/", fn, 0, cb_data); } -int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags", fn, 10); + return do_for_each_ref("refs/tags/", fn, 10, cb_data); } -int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads", fn, 11); + return do_for_each_ref("refs/heads/", fn, 11, cb_data); } -int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes", fn, 13); + return do_for_each_ref("refs/remotes/", fn, 13, cb_data); } +/* NEEDSWORK: This is only used by ssh-upload and it should go; the + * caller should do resolve_ref or read_ref like everybody else. Or + * maybe everybody else should use get_ref_sha1() instead of doing + * read_ref(). + */ int get_ref_sha1(const char *ref, unsigned char *sha1) { if (check_ref_format(ref)) return -1; - return read_ref(git_path("refs/%s", ref), sha1); + return read_ref(mkpath("refs/%s", ref), sha1); } /* @@ -273,22 +467,13 @@ int check_ref_format(const char *ref) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - char buf[40]; - int nr, fd = open(lock->ref_file, O_RDONLY); - if (fd < 0 && (mustexist || errno != ENOENT)) { - error("Can't verify ref %s", lock->ref_file); - unlock_ref(lock); - return NULL; - } - nr = read(fd, buf, 40); - close(fd); - if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) { - error("Can't verify ref %s", lock->ref_file); + if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); return NULL; } if (hashcmp(lock->old_sha1, old_sha1)) { - error("Ref %s is at %s but expected %s", lock->ref_file, + error("Ref %s is at %s but expected %s", lock->ref_name, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); return NULL; @@ -296,54 +481,223 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *path, - int plen, - const unsigned char *old_sha1, int mustexist) +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) { - const char *orig_path = path; + /* 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, int *flag) +{ + char *ref_file; + 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)); lock->lock_fd = -1; - plen = strlen(path) - plen; - path = resolve_ref(path, lock->old_sha1, mustexist); - if (!path) { - int last_errno = errno; + 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; + * it is normal for the empty directory 'foo' + * to remain. + */ + ref_file = git_path("%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, flag); + } + if (!ref) { + last_errno = errno; error("unable to resolve reference %s: %s", - orig_path, strerror(errno)); - unlock_ref(lock); - errno = last_errno; - return NULL; + 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_file = xstrdup(path); - lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen)); - lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; + lock->ref_name = xstrdup(ref); + lock->log_file = xstrdup(git_path("logs/%s", ref)); + ref_file = git_path("%s", ref); + lock->force_write = lstat(ref_file, &st) && errno == ENOENT; - if (safe_create_leading_directories(lock->ref_file)) - die("unable to create directory for %s", lock->ref_file); - lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1); + 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, int mustexist) +struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) { + char refpath[PATH_MAX]; if (check_ref_format(ref)) return NULL; - return lock_ref_sha1_basic(git_path("refs/%s", ref), - 5 + strlen(ref), old_sha1, mustexist); + strcpy(refpath, mkpath("refs/%s", ref)); + 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, int mustexist) +struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) +{ + return lock_ref_sha1_basic(ref, old_sha1, NULL); +} + +static struct lock_file packlock; + +static int repack_without_ref(const char *refname) { - return lock_ref_sha1_basic(git_path("%s", ref), - strlen(ref), old_sha1, mustexist); + struct ref_list *list, *packed_ref_list; + int fd; + int found = 0; + + 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) @@ -354,7 +708,7 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - free(lock->ref_file); + free(lock->ref_name); free(lock->log_file); free(lock); } @@ -367,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock, char *logrec; const char *committer; - if (log_all_ref_updates) { + if (log_all_ref_updates && + !strncmp(lock->ref_name, "refs/heads/", 11)) { if (safe_create_leading_directories(lock->log_file) < 0) return error("unable to create directory for %s", lock->log_file); @@ -376,10 +731,20 @@ static int log_ref_write(struct ref_lock *lock, logfd = open(lock->log_file, oflags, 0666); if (logfd < 0) { - if (!log_all_ref_updates && errno == ENOENT) + if (!(oflags & O_CREAT) && errno == ENOENT) return 0; - return error("Unable to append to %s: %s", - lock->log_file, strerror(errno)); + + if ((oflags & O_CREAT) && errno == EISDIR) { + if (remove_empty_directories(lock->log_file)) { + return error("There are still logs under '%s'", + lock->log_file); + } + logfd = open(lock->log_file, oflags, 0666); + } + + if (logfd < 0) + return error("Unable to append to %s: %s", + lock->log_file, strerror(errno)); } committer = git_committer_info(1); @@ -426,12 +791,13 @@ 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; } if (commit_lock_file(lock->lk)) { - error("Couldn't set %s", lock->ref_file); + error("Couldn't set %s", lock->ref_name); unlock_ref(lock); return -1; } @@ -440,7 +806,7 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) +int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1) { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; @@ -473,7 +839,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) if (!lastgt) die("Log %s is corrupt.", logfile); date = strtoul(lastgt + 1, &tz_c, 10); - if (date <= at_time) { + if (date <= at_time || cnt == 0) { if (lastrec) { if (get_sha1_hex(lastrec, logged_sha1)) die("Log %s is corrupt.", logfile); @@ -504,6 +870,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) return 0; } lastrec = rec; + if (cnt > 0) + cnt--; } rec = logdata; |