X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=refs.c;h=12e46b8bbefb35e5d402b3c8ef20dd9e4263e81a;hb=717d1462ba0ebc430775eccaae65eb9b8234eb32;hp=f003a0b1080267b419296b3b37312b858b8b215d;hpb=dc8302d27439b38cb3042b2d1c1106d8ef99e3f2;p=git.git diff --git a/refs.c b/refs.c index f003a0b10..12e46b8bb 100644 --- a/refs.c +++ b/refs.c @@ -1,12 +1,16 @@ -#include "refs.h" #include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" -#include +/* ISSYMREF=01 and ISPACKED=02 are public interfaces */ +#define REF_KNOWS_PEELED 04 struct ref_list { struct ref_list *next; unsigned char flag; /* ISSYMREF? ISPACKED? */ unsigned char sha1[20]; + unsigned char peeled[20]; char name[FLEX_ARRAY]; }; @@ -34,11 +38,13 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) 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 flag, struct ref_list *list, + struct ref_list **new_entry) { int len; struct ref_list **p = &list, *entry; @@ -50,8 +56,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, break; /* Same as existing entry? */ - if (!cmp) + if (!cmp) { + if (new_entry) + *new_entry = entry; return list; + } p = &entry->next; } @@ -59,10 +68,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, len = strlen(name) + 1; entry = xmalloc(sizeof(struct ref_list) + len); hashcpy(entry->sha1, sha1); + hashclr(entry->peeled); memcpy(entry->name, name, len); entry->flag = flag; entry->next = *p; *p = entry; + if (new_entry) + *new_entry = entry; return list; } @@ -98,25 +110,50 @@ static void invalidate_cached_refs(void) ca->did_loose = ca->did_packed = 0; } +static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) +{ + struct ref_list *list = NULL; + struct ref_list *last = NULL; + char refline[PATH_MAX]; + int flag = REF_ISPACKED; + + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name; + static const char header[] = "# pack-refs with:"; + + if (!strncmp(refline, header, sizeof(header)-1)) { + const char *traits = refline + sizeof(header) - 1; + if (strstr(traits, " peeled ")) + flag |= REF_KNOWS_PEELED; + /* perhaps other traits later as well */ + continue; + } + + name = parse_ref_line(refline, sha1); + if (name) { + list = add_ref(name, sha1, flag, list, &last); + continue; + } + if (last && + refline[0] == '^' && + strlen(refline) == 42 && + refline[41] == '\n' && + !get_sha1_hex(refline + 1, sha1)) + hashcpy(last->peeled, sha1); + } + cached_refs->packed = list; +} + 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"); + cached_refs.packed = NULL; 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); - } + read_packed_refs(f, &cached_refs); fclose(f); - refs = list; } - cached_refs.packed = refs; cached_refs.did_packed = 1; } return cached_refs.packed; @@ -159,7 +196,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) error("%s points nowhere!", ref); continue; } - list = add_ref(ref, sha1, flag, list); + list = add_ref(ref, sha1, flag, list, NULL); } free(ref); closedir(dir); @@ -247,7 +284,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * fd = open(path, O_RDONLY); if (fd < 0) return NULL; - len = read(fd, buffer, sizeof(buffer)-1); + len = read_in_full(fd, buffer, sizeof(buffer)-1); close(fd); /* @@ -294,8 +331,12 @@ int create_symref(const char *ref_target, const char *refs_heads_master) return -1; } lockpath = mkpath("%s.lock", git_HEAD); - fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); - written = write(fd, ref, len); + fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) { + error("Unable to open %s for writing", lockpath); + return -5; + } + written = write_in_full(fd, ref, len); close(fd); if (written != len) { unlink(lockpath); @@ -322,6 +363,57 @@ int read_ref(const char *ref, unsigned char *sha1) return -1; } +static int do_one_ref(const char *base, each_ref_fn fn, int trim, + void *cb_data, struct ref_list *entry) +{ + if (strncmp(base, entry->name, trim)) + return 0; + if (is_null_sha1(entry->sha1)) + return 0; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + return 0; + } + return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); +} + +int peel_ref(const char *ref, unsigned char *sha1) +{ + int flag; + unsigned char base[20]; + struct object *o; + + if (!resolve_ref(ref, base, 1, &flag)) + return -1; + + if ((flag & REF_ISPACKED)) { + struct ref_list *list = get_packed_refs(); + + while (list) { + if (!strcmp(list->name, ref)) { + if (list->flag & REF_KNOWS_PEELED) { + hashcpy(sha1, list->peeled); + return 0; + } + /* older pack-refs did not leave peeled ones */ + break; + } + list = list->next; + } + } + + /* fallback - callers should not call this for unpacked refs */ + o = parse_object(base); + if (o->type == OBJ_TAG) { + o = deref_tag(o, ref, 0); + if (o) { + hashcpy(sha1, o->sha1); + return 0; + } + } + return -1; +} + static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { @@ -343,29 +435,15 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, 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); + retval = do_one_ref(base, fn, trim, cb_data, entry); if (retval) return retval; } - 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) - return retval; - } - packed = packed->next; + for (packed = packed ? packed : loose; packed; packed = packed->next) { + retval = do_one_ref(base, fn, trim, cb_data, packed); + if (retval) + return retval; } return 0; } @@ -458,7 +536,7 @@ int check_ref_format(const char *ref) level++; if (!ch) { if (level < 2) - return -1; /* at least of form "heads/blah" */ + return -2; /* at least of form "heads/blah" */ return 0; } } @@ -534,6 +612,29 @@ static int remove_empty_directories(char *file) return remove_empty_dir_recursive(path, len); } +static int is_refname_available(const char *ref, const char *oldref, + struct ref_list *list, int quiet) +{ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + if (!oldref || strcmp(oldref, list->name)) { + 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] == '/') { + if (!quiet) + error("'%s' exists; cannot create '%s'", + list->name, ref); + return 0; + } + } + list = list->next; + } + return 1; +} + static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) { char *ref_file; @@ -567,29 +668,14 @@ 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; - } - } + /* When 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. + */ + if (is_null_sha1(lock->old_sha1) && + !is_refname_available(ref, NULL, get_packed_refs(), 0)) + goto error_return; lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -644,7 +730,6 @@ static int repack_without_ref(const char *refname) } 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); @@ -700,6 +785,132 @@ int delete_ref(const char *refname, unsigned char *sha1) return ret; } +int rename_ref(const char *oldref, const char *newref, const char *logmsg) +{ + static const char renamed_ref[] = "RENAMED-REF"; + unsigned char sha1[20], orig_sha1[20]; + int flag = 0, logmoved = 0; + struct ref_lock *lock; + struct stat loginfo; + int log = !lstat(git_path("logs/%s", oldref), &loginfo); + + if (S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", oldref); + + if (!resolve_ref(oldref, orig_sha1, 1, &flag)) + return error("refname %s not found", oldref); + + if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) + return 1; + + if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) + return 1; + + lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL); + if (!lock) + return error("unable to lock %s", renamed_ref); + lock->force_write = 1; + if (write_ref_sha1(lock, orig_sha1, logmsg)) + return error("unable to save current sha1 in %s", renamed_ref); + + if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log"))) + return error("unable to move logfile logs/%s to tmp-renamed-log: %s", + oldref, strerror(errno)); + + if (delete_ref(oldref, orig_sha1)) { + error("unable to delete old %s", oldref); + goto rollback; + } + + if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("%s", newref))) { + error("Directory not empty: %s", newref); + goto rollback; + } + } else { + error("unable to delete existing %s", newref); + goto rollback; + } + } + + if (log && safe_create_leading_directories(git_path("logs/%s", newref))) { + error("unable to create directory for %s", newref); + goto rollback; + } + + retry: + if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) { + if (errno==EISDIR || errno==ENOTDIR) { + /* + * rename(a, b) when b is an existing + * directory ought to result in ISDIR, but + * Solaris 5.8 gives ENOTDIR. Sheesh. + */ + if (remove_empty_directories(git_path("logs/%s", newref))) { + error("Directory not empty: logs/%s", newref); + goto rollback; + } + goto retry; + } else { + error("unable to move logfile tmp-renamed-log to logs/%s: %s", + newref, strerror(errno)); + goto rollback; + } + } + logmoved = log; + + lock = lock_ref_sha1_basic(newref, NULL, NULL); + if (!lock) { + error("unable to lock %s for update", newref); + goto rollback; + } + + lock->force_write = 1; + hashcpy(lock->old_sha1, orig_sha1); + if (write_ref_sha1(lock, orig_sha1, logmsg)) { + error("unable to write current sha1 into %s", newref); + goto rollback; + } + + if (!strncmp(oldref, "refs/heads/", 11) && + !strncmp(newref, "refs/heads/", 11)) { + char oldsection[1024], newsection[1024]; + + snprintf(oldsection, 1024, "branch.%s", oldref + 11); + snprintf(newsection, 1024, "branch.%s", newref + 11); + if (git_config_rename_section(oldsection, newsection) < 0) + return 1; + } + + return 0; + + rollback: + lock = lock_ref_sha1_basic(oldref, NULL, NULL); + if (!lock) { + error("unable to lock %s for rollback", oldref); + goto rollbacklog; + } + + lock->force_write = 1; + flag = log_all_ref_updates; + log_all_ref_updates = 0; + if (write_ref_sha1(lock, orig_sha1, NULL)) + error("unable to write current sha1 into %s", oldref); + log_all_ref_updates = flag; + + rollbacklog: + if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from %s: %s", + oldref, newref, strerror(errno)); + if (!logmoved && log && + rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from tmp-renamed-log: %s", + oldref, strerror(errno)); + + return 1; +} + void unlock_ref(struct ref_lock *lock) { if (lock->lock_fd >= 0) { @@ -718,11 +929,16 @@ static int log_ref_write(struct ref_lock *lock, { int logfd, written, oflags = O_APPEND | O_WRONLY; unsigned maxlen, len; + int msglen; char *logrec; const char *committer; + if (log_all_ref_updates < 0) + log_all_ref_updates = !is_bare_repository(); + if (log_all_ref_updates && - !strncmp(lock->ref_name, "refs/heads/", 11)) { + (!strncmp(lock->ref_name, "refs/heads/", 11) || + !strncmp(lock->ref_name, "refs/remotes/", 13))) { if (safe_create_leading_directories(lock->log_file) < 0) return error("unable to create directory for %s", lock->log_file); @@ -747,25 +963,31 @@ static int log_ref_write(struct ref_lock *lock, lock->log_file, strerror(errno)); } - committer = git_committer_info(1); + msglen = 0; if (msg) { - maxlen = strlen(committer) + strlen(msg) + 2*40 + 5; - logrec = xmalloc(maxlen); - len = snprintf(logrec, maxlen, "%s %s %s\t%s\n", - sha1_to_hex(lock->old_sha1), - sha1_to_hex(sha1), - committer, - msg); - } - else { - maxlen = strlen(committer) + 2*40 + 4; - logrec = xmalloc(maxlen); - len = snprintf(logrec, maxlen, "%s %s %s\n", - sha1_to_hex(lock->old_sha1), - sha1_to_hex(sha1), - committer); - } - written = len <= maxlen ? write(logfd, logrec, len) : -1; + /* clean up the message and make sure it is a single line */ + for ( ; *msg; msg++) + if (!isspace(*msg)) + break; + if (*msg) { + const char *ep = strchr(msg, '\n'); + if (ep) + msglen = ep - msg; + else + msglen = strlen(msg); + } + } + + committer = git_committer_info(-1); + maxlen = strlen(committer) + msglen + 100; + logrec = xmalloc(maxlen); + len = sprintf(logrec, "%s %s %s\n", + sha1_to_hex(lock->old_sha1), + sha1_to_hex(sha1), + committer); + if (msglen) + len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1; + written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1; free(logrec); close(logfd); if (written != len) @@ -784,8 +1006,8 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return 0; } - if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 || - write(lock->lock_fd, &term, 1) != 1 + if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 || + write_in_full(lock->lock_fd, &term, 1) != 1 || close(lock->lock_fd) < 0) { error("Couldn't write %s", lock->lk->filename); unlock_ref(lock); @@ -806,14 +1028,29 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1) +static char *ref_msg(const char *line, const char *endp) +{ + const char *ep; + char *msg; + + line += 82; + for (ep = line; ep < endp && *ep != '\n'; ep++) + ; + msg = xmalloc(ep - line + 1); + memcpy(msg, line, ep - line); + msg[ep - line] = 0; + return msg; +} + +int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt) { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; - int logfd, tz; + int logfd, tz, reccnt = 0; struct stat st; unsigned long date; unsigned char logged_sha1[20]; + void *log_mapped; logfile = git_path("logs/%s", ref); logfd = open(logfile, O_RDONLY, 0); @@ -822,12 +1059,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * fstat(logfd, &st); if (!st.st_size) die("Log %s is empty.", logfile); - logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0); + log_mapped = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0); + logdata = log_mapped; close(logfd); lastrec = NULL; rec = logend = logdata + st.st_size; while (logdata < rec) { + reccnt++; if (logdata < rec && *(rec-1) == '\n') rec--; lastgt = NULL; @@ -840,13 +1079,21 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * die("Log %s is corrupt.", logfile); date = strtoul(lastgt + 1, &tz_c, 10); if (date <= at_time || cnt == 0) { + tz = strtoul(tz_c, NULL, 10); + if (msg) + *msg = ref_msg(rec, logend); + if (cutoff_time) + *cutoff_time = date; + if (cutoff_tz) + *cutoff_tz = tz; + if (cutoff_cnt) + *cutoff_cnt = reccnt - 1; if (lastrec) { if (get_sha1_hex(lastrec, logged_sha1)) die("Log %s is corrupt.", logfile); if (get_sha1_hex(rec + 41, sha1)) die("Log %s is corrupt.", logfile); if (hashcmp(logged_sha1, sha1)) { - tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s has gap after %s.\n", logfile, show_rfc2822_date(date, tz)); @@ -860,13 +1107,12 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * if (get_sha1_hex(rec + 41, logged_sha1)) die("Log %s is corrupt.", logfile); if (hashcmp(logged_sha1, sha1)) { - tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s unexpectedly ended on %s.\n", logfile, show_rfc2822_date(date, tz)); } } - munmap((void*)logdata, st.st_size); + munmap(log_mapped, st.st_size); return 0; } lastrec = rec; @@ -883,8 +1129,58 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * tz = strtoul(tz_c, NULL, 10); if (get_sha1_hex(logdata, sha1)) die("Log %s is corrupt.", logfile); - munmap((void*)logdata, st.st_size); - fprintf(stderr, "warning: Log %s only goes back to %s.\n", - logfile, show_rfc2822_date(date, tz)); - return 0; + if (msg) + *msg = ref_msg(logdata, logend); + munmap(log_mapped, st.st_size); + + if (cutoff_time) + *cutoff_time = date; + if (cutoff_tz) + *cutoff_tz = tz; + if (cutoff_cnt) + *cutoff_cnt = reccnt; + return 1; +} + +int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +{ + const char *logfile; + FILE *logfp; + char buf[1024]; + int ret = 0; + + logfile = git_path("logs/%s", ref); + logfp = fopen(logfile, "r"); + if (!logfp) + return -1; + while (fgets(buf, sizeof(buf), logfp)) { + unsigned char osha1[20], nsha1[20]; + char *email_end, *message; + unsigned long timestamp; + int len, tz; + + /* old SP new SP name SP time TAB msg LF */ + len = strlen(buf); + if (len < 83 || buf[len-1] != '\n' || + get_sha1_hex(buf, osha1) || buf[40] != ' ' || + get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' || + !(email_end = strchr(buf + 82, '>')) || + email_end[1] != ' ' || + !(timestamp = strtoul(email_end + 2, &message, 10)) || + !message || message[0] != ' ' || + (message[1] != '+' && message[1] != '-') || + !isdigit(message[2]) || !isdigit(message[3]) || + !isdigit(message[4]) || !isdigit(message[5]) || + message[6] != '\t') + continue; /* corrupt? */ + email_end[1] = '\0'; + tz = strtol(message + 1, NULL, 10); + message += 7; + ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data); + if (ret) + break; + } + fclose(logfp); + return ret; } +