diff options
Diffstat (limited to 'refs.c')
-rw-r--r-- | refs.c | 785 |
1 files changed, 507 insertions, 278 deletions
@@ -6,8 +6,34 @@ #include "string-list.h" /* - * Make sure "ref" is something reasonable to have under ".git/refs/"; - * We do not like it if: + * How to handle various characters in refnames: + * 0: An acceptable character for refs + * 1: End-of-component + * 2: ., look for a preceding . to reject .. in refs + * 3: {, look for a preceding @ to reject @{ in refs + * 4: A bad character: ASCII control characters, "~", "^", ":" or SP + */ +static unsigned char refname_disposition[256] = { + 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4 +}; + +/* + * Used as a flag to ref_transaction_delete when a loose ref is being + * pruned. + */ +#define REF_ISPRUNING 0x0100 +/* + * Try to read one refname component from the front of refname. + * Return the length of the component found, or -1 if the component is + * not legal. It is legal if it is something reasonable to have under + * ".git/refs/"; We do not like it if: * * - any path component of it begins with ".", or * - it has double dots "..", or @@ -16,41 +42,31 @@ * - it ends with ".lock" * - it contains a "\" (backslash) */ - -/* Return true iff ch is not allowed in reference names. */ -static inline int bad_ref_char(int ch) -{ - if (((unsigned) ch) <= ' ' || ch == 0x7f || - ch == '~' || ch == '^' || ch == ':' || ch == '\\') - return 1; - /* 2.13 Pattern Matching Notation */ - if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */ - return 1; - return 0; -} - -/* - * Try to read one refname component from the front of refname. Return - * the length of the component found, or -1 if the component is not - * legal. - */ static int check_refname_component(const char *refname, int flags) { const char *cp; char last = '\0'; for (cp = refname; ; cp++) { - char ch = *cp; - if (ch == '\0' || ch == '/') + int ch = *cp & 255; + unsigned char disp = refname_disposition[ch]; + switch (disp) { + case 1: + goto out; + case 2: + if (last == '.') + return -1; /* Refname contains "..". */ break; - if (bad_ref_char(ch)) - return -1; /* Illegal character in refname. */ - if (last == '.' && ch == '.') - return -1; /* Refname contains "..". */ - if (last == '@' && ch == '{') - return -1; /* Refname contains "@{". */ + case 3: + if (last == '@') + return -1; /* Refname contains "@{". */ + break; + case 4: + return -1; + } last = ch; } +out: if (cp == refname) return 0; /* Component has zero length. */ if (refname[0] == '.') { @@ -1151,7 +1167,7 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) if (de->d_name[0] == '.') continue; - if (has_extension(de->d_name, ".lock")) + if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(&refname, de->d_name); refdir = *refs->name @@ -1323,6 +1339,7 @@ static const char *handle_missing_loose_ref(const char *refname, } } +/* This function needs to return a meaningful errno on failure */ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH; @@ -1333,8 +1350,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea if (flag) *flag = 0; - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + errno = EINVAL; return NULL; + } for (;;) { char path[PATH_MAX]; @@ -1342,8 +1361,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea char *buf; int fd; - if (--depth < 0) + if (--depth < 0) { + errno = ELOOP; return NULL; + } git_snpath(path, sizeof(path), "%s", refname); @@ -1405,9 +1426,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea return NULL; } len = read_in_full(fd, buffer, sizeof(buffer)-1); - close(fd); - if (len < 0) + if (len < 0) { + int save_errno = errno; + close(fd); + errno = save_errno; return NULL; + } + close(fd); while (len && isspace(buffer[len-1])) len--; buffer[len] = '\0'; @@ -1424,6 +1449,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea (buffer[40] != '\0' && !isspace(buffer[40]))) { if (flag) *flag |= REF_ISBROKEN; + errno = EINVAL; return NULL; } return refname; @@ -1436,6 +1462,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { if (flag) *flag |= REF_ISBROKEN; + errno = EINVAL; return NULL; } refname = strcpy(refname_buffer, buf); @@ -1520,9 +1547,8 @@ static enum peel_status peel_object(const unsigned char *name, unsigned char *sh if (o->type == OBJ_NONE) { int type = sha1_object_info(name, NULL); - if (type < 0) + if (type < 0 || !object_as_type(o, type, 0)) return PEEL_INVALID; - o->type = type; } if (o->type != OBJ_TAG) @@ -1921,18 +1947,22 @@ int refname_match(const char *abbrev_name, const char *full_name) return 0; } +/* This function should make sure errno is meaningful on error */ static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + int save_errno = errno; error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); + errno = save_errno; return NULL; } if (hashcmp(lock->old_sha1, old_sha1)) { 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); + errno = EBUSY; return NULL; } return lock; @@ -1945,14 +1975,16 @@ static int remove_empty_directories(const char *file) * only empty directories), remove them. */ struct strbuf path; - int result; + int result, save_errno; strbuf_init(&path, 20); strbuf_addstr(&path, file); result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY); + save_errno = errno; strbuf_release(&path); + errno = save_errno; return result; } @@ -2016,7 +2048,6 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) *log = NULL; for (p = ref_rev_parse_rules; *p; p++) { - struct stat st; unsigned char hash[20]; char path[PATH_MAX]; const char *ref, *it; @@ -2025,12 +2056,9 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) ref = resolve_ref_unsafe(path, hash, 1, NULL); if (!ref) continue; - if (!stat(git_path("logs/%s", path), &st) && - S_ISREG(st.st_mode)) + if (reflog_exists(path)) it = path; - else if (strcmp(ref, path) && - !stat(git_path("logs/%s", ref), &st) && - S_ISREG(st.st_mode)) + else if (strcmp(ref, path) && reflog_exists(ref)) it = ref; else continue; @@ -2045,6 +2073,10 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) return logs_found; } +/* + * Locks a "refs/" ref returning the lock on success and NULL on failure. + * On failure errno is set to something meaningful. + */ static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) @@ -2145,15 +2177,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, return NULL; } -struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1) -{ - char refpath[PATH_MAX]; - if (check_refname_format(refname, 0)) - return NULL; - strcpy(refpath, mkpath("refs/%s", refname)); - return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL); -} - struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) @@ -2205,6 +2228,7 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data) return 0; } +/* This should return a meaningful errno on failure */ int lock_packed_refs(int flags) { struct packed_ref_cache *packed_ref_cache; @@ -2224,11 +2248,16 @@ int lock_packed_refs(int flags) return 0; } +/* + * Commit the packed refs changes. + * On error we must make sure that errno contains a meaningful value. + */ int commit_packed_refs(void) { struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(&ref_cache); int error = 0; + int save_errno = 0; if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@ -2238,10 +2267,13 @@ int commit_packed_refs(void) do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache), 0, write_packed_entry_fn, &packed_ref_cache->lock->fd); - if (commit_lock_file(packed_ref_cache->lock)) + if (commit_lock_file(packed_ref_cache->lock)) { + save_errno = errno; error = -1; + } packed_ref_cache->lock = NULL; release_packed_ref_cache(packed_ref_cache); + errno = save_errno; return error; } @@ -2354,13 +2386,25 @@ static void try_remove_empty_parents(char *name) /* make sure nobody touched the ref, and unlink */ static void prune_ref(struct ref_to_prune *r) { - struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1); + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; - if (lock) { - unlink_or_warn(git_path("%s", r->name)); - unlock_ref(lock); - try_remove_empty_parents(r->name); + if (check_refname_format(r->name + 5, 0)) + return; + + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_delete(transaction, r->name, r->sha1, + REF_ISPRUNING, 1, &err) || + ref_transaction_commit(transaction, NULL, &err)) { + ref_transaction_free(transaction); + error("%s", err.buf); + strbuf_release(&err); + return; } + ref_transaction_free(transaction); + strbuf_release(&err); + try_remove_empty_parents(r->name); } static void prune_refs(struct ref_to_prune *r) @@ -2448,12 +2492,12 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data) return 0; } -int repack_without_refs(const char **refnames, int n) +int repack_without_refs(const char **refnames, int n, struct strbuf *err) { struct ref_dir *packed; struct string_list refs_to_delete = STRING_LIST_INIT_DUP; struct string_list_item *ref_to_delete; - int i, removed = 0; + int i, ret, removed = 0; /* Look for a packed ref */ for (i = 0; i < n; i++) @@ -2465,6 +2509,11 @@ int repack_without_refs(const char **refnames, int n) return 0; /* no refname exists in packed refs */ if (lock_packed_refs(0)) { + if (err) { + unable_to_lock_message(git_path("packed-refs"), errno, + err); + return -1; + } unable_to_lock_error(git_path("packed-refs"), errno); return error("cannot delete '%s' from packed refs", refnames[i]); } @@ -2491,12 +2540,11 @@ int repack_without_refs(const char **refnames, int n) } /* Write what remains */ - return commit_packed_refs(); -} - -static int repack_without_ref(const char *refname) -{ - return repack_without_refs(&refname, 1); + ret = commit_packed_refs(); + if (ret && err) + strbuf_addf(err, "unable to overwrite old ref-pack file: %s", + strerror(errno)); + return ret; } static int delete_ref_loose(struct ref_lock *lock, int flag) @@ -2516,24 +2564,22 @@ static int delete_ref_loose(struct ref_lock *lock, int flag) int delete_ref(const char *refname, const unsigned char *sha1, int delopt) { - struct ref_lock *lock; - int ret = 0, flag = 0; - - lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag); - if (!lock) + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_delete(transaction, refname, sha1, delopt, + sha1 && !is_null_sha1(sha1), &err) || + ref_transaction_commit(transaction, NULL, &err)) { + error("%s", err.buf); + ref_transaction_free(transaction); + strbuf_release(&err); return 1; - ret |= delete_ref_loose(lock, flag); - - /* 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(lock->ref_name); - - unlink_or_warn(git_path("logs/%s", lock->ref_name)); - clear_loose_ref_cache(&ref_cache); - unlock_ref(lock); - return ret; + } + ref_transaction_free(transaction); + strbuf_release(&err); + return 0; } /* @@ -2734,6 +2780,7 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } +/* This function must set a meaningful errno on failure */ int log_ref_setup(const char *refname, char *logfile, int bufsize) { int logfd, oflags = O_APPEND | O_WRONLY; @@ -2744,9 +2791,12 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) starts_with(refname, "refs/remotes/") || starts_with(refname, "refs/notes/") || !strcmp(refname, "HEAD"))) { - if (safe_create_leading_directories(logfile) < 0) - return error("unable to create directory for %s", - logfile); + if (safe_create_leading_directories(logfile) < 0) { + int save_errno = errno; + error("unable to create directory for %s", logfile); + errno = save_errno; + return -1; + } oflags |= O_CREAT; } @@ -2757,15 +2807,22 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) if ((oflags & O_CREAT) && errno == EISDIR) { if (remove_empty_directories(logfile)) { - return error("There are still logs under '%s'", - logfile); + int save_errno = errno; + error("There are still logs under '%s'", + logfile); + errno = save_errno; + return -1; } logfd = open(logfile, oflags, 0666); } - if (logfd < 0) - return error("Unable to append to %s: %s", - logfile, strerror(errno)); + if (logfd < 0) { + int save_errno = errno; + error("Unable to append to %s: %s", logfile, + strerror(errno)); + errno = save_errno; + return -1; + } } adjust_shared_perm(logfile); @@ -2805,24 +2862,38 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, len += copy_msg(logrec + len - 1, msg) - 1; written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1; free(logrec); - if (close(logfd) != 0 || written != len) - return error("Unable to append to %s", log_file); + if (written != len) { + int save_errno = errno; + close(logfd); + error("Unable to append to %s", log_file); + errno = save_errno; + return -1; + } + if (close(logfd)) { + int save_errno = errno; + error("Unable to append to %s", log_file); + errno = save_errno; + return -1; + } return 0; } -static int is_branch(const char *refname) +int is_branch(const char *refname) { return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); } +/* This function must return a meaningful errno */ int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *logmsg) { static char term = '\n'; struct object *o; - if (!lock) + if (!lock) { + errno = EINVAL; return -1; + } if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { unlock_ref(lock); return 0; @@ -2832,19 +2903,23 @@ int write_ref_sha1(struct ref_lock *lock, error("Trying to write ref %s with nonexistent object %s", lock->ref_name, sha1_to_hex(sha1)); unlock_ref(lock); + errno = EINVAL; return -1; } if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { error("Trying to write non-commit object %s to branch %s", sha1_to_hex(sha1), lock->ref_name); unlock_ref(lock); + errno = EINVAL; return -1; } if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 || - write_in_full(lock->lock_fd, &term, 1) != 1 - || close_ref(lock) < 0) { + write_in_full(lock->lock_fd, &term, 1) != 1 || + close_ref(lock) < 0) { + int save_errno = errno; error("Couldn't write %s", lock->lk->filename); unlock_ref(lock); + errno = save_errno; return -1; } clear_loose_ref_cache(&ref_cache); @@ -2947,122 +3022,133 @@ int create_symref(const char *ref_target, const char *refs_heads_master, return 0; } -static char *ref_msg(const char *line, const char *endp) -{ - const char *ep; - line += 82; - ep = memchr(line, '\n', endp - line); - if (!ep) - ep = endp; - return xmemdupz(line, ep - line); +struct read_ref_at_cb { + const char *refname; + unsigned long at_time; + int cnt; + int reccnt; + unsigned char *sha1; + int found_it; + + unsigned char osha1[20]; + unsigned char nsha1[20]; + int tz; + unsigned long date; + char **msg; + unsigned long *cutoff_time; + int *cutoff_tz; + int *cutoff_cnt; +}; + +static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct read_ref_at_cb *cb = cb_data; + + cb->reccnt++; + cb->tz = tz; + cb->date = timestamp; + + if (timestamp <= cb->at_time || cb->cnt == 0) { + if (cb->msg) + *cb->msg = xstrdup(message); + if (cb->cutoff_time) + *cb->cutoff_time = timestamp; + if (cb->cutoff_tz) + *cb->cutoff_tz = tz; + if (cb->cutoff_cnt) + *cb->cutoff_cnt = cb->reccnt - 1; + /* + * we have not yet updated cb->[n|o]sha1 so they still + * hold the values for the previous record. + */ + if (!is_null_sha1(cb->osha1)) { + hashcpy(cb->sha1, nsha1); + if (hashcmp(cb->osha1, nsha1)) + warning("Log for ref %s has gap after %s.", + cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822)); + } + else if (cb->date == cb->at_time) + hashcpy(cb->sha1, nsha1); + else if (hashcmp(nsha1, cb->sha1)) + warning("Log for ref %s unexpectedly ended on %s.", + cb->refname, show_date(cb->date, cb->tz, + DATE_RFC2822)); + hashcpy(cb->osha1, osha1); + hashcpy(cb->nsha1, nsha1); + cb->found_it = 1; + return 1; + } + hashcpy(cb->osha1, osha1); + hashcpy(cb->nsha1, nsha1); + if (cb->cnt > 0) + cb->cnt--; + return 0; +} + +static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, + int tz, const char *message, void *cb_data) +{ + struct read_ref_at_cb *cb = cb_data; + + if (cb->msg) + *cb->msg = xstrdup(message); + if (cb->cutoff_time) + *cb->cutoff_time = timestamp; + if (cb->cutoff_tz) + *cb->cutoff_tz = tz; + if (cb->cutoff_cnt) + *cb->cutoff_cnt = cb->reccnt; + hashcpy(cb->sha1, osha1); + if (is_null_sha1(cb->sha1)) + hashcpy(cb->sha1, nsha1); + /* We just want the first entry */ + return 1; } int read_ref_at(const char *refname, 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, reccnt = 0; - struct stat st; - unsigned long date; - unsigned char logged_sha1[20]; - void *log_mapped; - size_t mapsz; + struct read_ref_at_cb cb; - logfile = git_path("logs/%s", refname); - logfd = open(logfile, O_RDONLY, 0); - if (logfd < 0) - die_errno("Unable to read log '%s'", logfile); - fstat(logfd, &st); - if (!st.st_size) - die("Log %s is empty.", logfile); - mapsz = xsize_t(st.st_size); - log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0); - logdata = log_mapped; - close(logfd); + memset(&cb, 0, sizeof(cb)); + cb.refname = refname; + cb.at_time = at_time; + cb.cnt = cnt; + cb.msg = msg; + cb.cutoff_time = cutoff_time; + cb.cutoff_tz = cutoff_tz; + cb.cutoff_cnt = cutoff_cnt; + cb.sha1 = sha1; + + for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb); + + if (!cb.reccnt) + die("Log for %s is empty.", refname); + if (cb.found_it) + return 0; + + for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb); - lastrec = NULL; - rec = logend = logdata + st.st_size; - while (logdata < rec) { - reccnt++; - if (logdata < rec && *(rec-1) == '\n') - rec--; - lastgt = NULL; - while (logdata < rec && *(rec-1) != '\n') { - rec--; - if (*rec == '>') - lastgt = rec; - } - if (!lastgt) - 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)) { - warning("Log %s has gap after %s.", - logfile, show_date(date, tz, DATE_RFC2822)); - } - } - else if (date == at_time) { - if (get_sha1_hex(rec + 41, sha1)) - die("Log %s is corrupt.", logfile); - } - else { - if (get_sha1_hex(rec + 41, logged_sha1)) - die("Log %s is corrupt.", logfile); - if (hashcmp(logged_sha1, sha1)) { - warning("Log %s unexpectedly ended on %s.", - logfile, show_date(date, tz, DATE_RFC2822)); - } - } - munmap(log_mapped, mapsz); - return 0; - } - lastrec = rec; - if (cnt > 0) - cnt--; - } - - rec = logdata; - while (rec < logend && *rec != '>' && *rec != '\n') - rec++; - if (rec == logend || *rec == '\n') - die("Log %s is corrupt.", logfile); - date = strtoul(rec + 1, &tz_c, 10); - tz = strtoul(tz_c, NULL, 10); - if (get_sha1_hex(logdata, sha1)) - die("Log %s is corrupt.", logfile); - if (is_null_sha1(sha1)) { - if (get_sha1_hex(logdata + 41, sha1)) - die("Log %s is corrupt.", logfile); - } - if (msg) - *msg = ref_msg(logdata, logend); - munmap(log_mapped, mapsz); - - if (cutoff_time) - *cutoff_time = date; - if (cutoff_tz) - *cutoff_tz = tz; - if (cutoff_cnt) - *cutoff_cnt = reccnt; return 1; } +int reflog_exists(const char *refname) +{ + struct stat st; + + return !lstat(git_path("logs/%s", refname), &st) && + S_ISREG(st.st_mode); +} + +int delete_reflog(const char *refname) +{ + return remove_path(git_path("logs/%s", refname)); +} + static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) { unsigned char osha1[20], nsha1[20]; @@ -3215,7 +3301,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data if (de->d_name[0] == '.') continue; - if (has_extension(de->d_name, ".lock")) + if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(name, de->d_name); if (stat(git_path("logs/%s", name->buf), &st) < 0) { @@ -3250,36 +3336,147 @@ int for_each_reflog(each_ref_fn fn, void *cb_data) return retval; } -static struct ref_lock *update_ref_lock(const char *refname, - const unsigned char *oldval, - int flags, int *type_p, - enum action_on_err onerr) -{ +/** + * Information needed for a single ref update. Set new_sha1 to the + * new value or to zero to delete the ref. To check the old value + * while locking the ref, set have_old to 1 and set old_sha1 to the + * value or to zero to ensure the ref does not exist before update. + */ +struct ref_update { + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int flags; /* REF_NODEREF? */ + int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ struct ref_lock *lock; - lock = lock_any_ref_for_update(refname, oldval, flags, type_p); - if (!lock) { - const char *str = "Cannot lock the ref '%s'."; - switch (onerr) { - case MSG_ON_ERR: error(str, refname); break; - case DIE_ON_ERR: die(str, refname); break; - case QUIET_ON_ERR: break; - } - } - return lock; + int type; + const char refname[FLEX_ARRAY]; +}; + +/* + * Transaction states. + * OPEN: The transaction is in a valid state and can accept new updates. + * An OPEN transaction can be committed. + * CLOSED: A closed transaction is no longer active and no other operations + * than free can be used on it in this state. + * A transaction can either become closed by successfully committing + * an active transaction or if there is a failure while building + * the transaction thus rendering it failed/inactive. + */ +enum ref_transaction_state { + REF_TRANSACTION_OPEN = 0, + REF_TRANSACTION_CLOSED = 1 +}; + +/* + * Data structure for holding a reference transaction, which can + * consist of checks and updates to multiple references, carried out + * as atomically as possible. This structure is opaque to callers. + */ +struct ref_transaction { + struct ref_update **updates; + size_t alloc; + size_t nr; + enum ref_transaction_state state; +}; + +struct ref_transaction *ref_transaction_begin(struct strbuf *err) +{ + return xcalloc(1, sizeof(struct ref_transaction)); } -static int update_ref_write(const char *action, const char *refname, - const unsigned char *sha1, struct ref_lock *lock, - enum action_on_err onerr) +void ref_transaction_free(struct ref_transaction *transaction) { - if (write_ref_sha1(lock, sha1, action) < 0) { - const char *str = "Cannot update the ref '%s'."; - switch (onerr) { - case MSG_ON_ERR: error(str, refname); break; - case DIE_ON_ERR: die(str, refname); break; - case QUIET_ON_ERR: break; - } - return 1; + int i; + + if (!transaction) + return; + + for (i = 0; i < transaction->nr; i++) + free(transaction->updates[i]); + + free(transaction->updates); + free(transaction); +} + +static struct ref_update *add_update(struct ref_transaction *transaction, + const char *refname) +{ + size_t len = strlen(refname); + struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1); + + strcpy((char *)update->refname, refname); + ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc); + transaction->updates[transaction->nr++] = update; + return update; +} + +int ref_transaction_update(struct ref_transaction *transaction, + const char *refname, + const unsigned char *new_sha1, + const unsigned char *old_sha1, + int flags, int have_old, + struct strbuf *err) +{ + struct ref_update *update; + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: update called for transaction that is not open"); + + if (have_old && !old_sha1) + die("BUG: have_old is true but old_sha1 is NULL"); + + update = add_update(transaction, refname); + hashcpy(update->new_sha1, new_sha1); + update->flags = flags; + update->have_old = have_old; + if (have_old) + hashcpy(update->old_sha1, old_sha1); + return 0; +} + +int ref_transaction_create(struct ref_transaction *transaction, + const char *refname, + const unsigned char *new_sha1, + int flags, + struct strbuf *err) +{ + struct ref_update *update; + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: create called for transaction that is not open"); + + if (!new_sha1 || is_null_sha1(new_sha1)) + die("BUG: create ref with null new_sha1"); + + update = add_update(transaction, refname); + + hashcpy(update->new_sha1, new_sha1); + hashclr(update->old_sha1); + update->flags = flags; + update->have_old = 1; + return 0; +} + +int ref_transaction_delete(struct ref_transaction *transaction, + const char *refname, + const unsigned char *old_sha1, + int flags, int have_old, + struct strbuf *err) +{ + struct ref_update *update; + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: delete called for transaction that is not open"); + + if (have_old && !old_sha1) + die("BUG: have_old is true but old_sha1 is NULL"); + + update = add_update(transaction, refname); + update->flags = flags; + update->have_old = have_old; + if (have_old) { + assert(!is_null_sha1(old_sha1)); + hashcpy(update->old_sha1, old_sha1); } return 0; } @@ -3288,109 +3485,141 @@ int update_ref(const char *action, const char *refname, const unsigned char *sha1, const unsigned char *oldval, int flags, enum action_on_err onerr) { - struct ref_lock *lock; - lock = update_ref_lock(refname, oldval, flags, NULL, onerr); - if (!lock) + struct ref_transaction *t; + struct strbuf err = STRBUF_INIT; + + t = ref_transaction_begin(&err); + if (!t || + ref_transaction_update(t, refname, sha1, oldval, flags, + !!oldval, &err) || + ref_transaction_commit(t, action, &err)) { + const char *str = "update_ref failed for ref '%s': %s"; + + ref_transaction_free(t); + switch (onerr) { + case UPDATE_REFS_MSG_ON_ERR: + error(str, refname, err.buf); + break; + case UPDATE_REFS_DIE_ON_ERR: + die(str, refname, err.buf); + break; + case UPDATE_REFS_QUIET_ON_ERR: + break; + } + strbuf_release(&err); return 1; - return update_ref_write(action, refname, sha1, lock, onerr); + } + strbuf_release(&err); + ref_transaction_free(t); + return 0; } static int ref_update_compare(const void *r1, const void *r2) { const struct ref_update * const *u1 = r1; const struct ref_update * const *u2 = r2; - return strcmp((*u1)->ref_name, (*u2)->ref_name); + return strcmp((*u1)->refname, (*u2)->refname); } static int ref_update_reject_duplicates(struct ref_update **updates, int n, - enum action_on_err onerr) + struct strbuf *err) { int i; for (i = 1; i < n; i++) - if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) { + if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) { const char *str = "Multiple updates for ref '%s' not allowed."; - switch (onerr) { - case MSG_ON_ERR: - error(str, updates[i]->ref_name); break; - case DIE_ON_ERR: - die(str, updates[i]->ref_name); break; - case QUIET_ON_ERR: - break; - } + if (err) + strbuf_addf(err, str, updates[i]->refname); + return 1; } return 0; } -int update_refs(const char *action, const struct ref_update **updates_orig, - int n, enum action_on_err onerr) +int ref_transaction_commit(struct ref_transaction *transaction, + const char *msg, struct strbuf *err) { int ret = 0, delnum = 0, i; - struct ref_update **updates; - int *types; - struct ref_lock **locks; const char **delnames; + int n = transaction->nr; + struct ref_update **updates = transaction->updates; - if (!updates_orig || !n) + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: commit called for transaction that is not open"); + + if (!n) { + transaction->state = REF_TRANSACTION_CLOSED; return 0; + } /* Allocate work space */ - updates = xmalloc(sizeof(*updates) * n); - types = xmalloc(sizeof(*types) * n); - locks = xcalloc(n, sizeof(*locks)); delnames = xmalloc(sizeof(*delnames) * n); /* Copy, sort, and reject duplicate refs */ - memcpy(updates, updates_orig, sizeof(*updates) * n); qsort(updates, n, sizeof(*updates), ref_update_compare); - ret = ref_update_reject_duplicates(updates, n, onerr); + ret = ref_update_reject_duplicates(updates, n, err); if (ret) goto cleanup; /* Acquire all locks while verifying old values */ for (i = 0; i < n; i++) { - locks[i] = update_ref_lock(updates[i]->ref_name, - (updates[i]->have_old ? - updates[i]->old_sha1 : NULL), - updates[i]->flags, - &types[i], onerr); - if (!locks[i]) { + struct ref_update *update = updates[i]; + + update->lock = lock_any_ref_for_update(update->refname, + (update->have_old ? + update->old_sha1 : + NULL), + update->flags, + &update->type); + if (!update->lock) { + if (err) + strbuf_addf(err, "Cannot lock the ref '%s'.", + update->refname); ret = 1; goto cleanup; } } /* Perform updates first so live commits remain referenced */ - for (i = 0; i < n; i++) - if (!is_null_sha1(updates[i]->new_sha1)) { - ret = update_ref_write(action, - updates[i]->ref_name, - updates[i]->new_sha1, - locks[i], onerr); - locks[i] = NULL; /* freed by update_ref_write */ - if (ret) + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if (!is_null_sha1(update->new_sha1)) { + ret = write_ref_sha1(update->lock, update->new_sha1, + msg); + update->lock = NULL; /* freed by write_ref_sha1 */ + if (ret) { + if (err) + strbuf_addf(err, "Cannot update the ref '%s'.", + update->refname); goto cleanup; + } } + } /* Perform deletes now that updates are safely completed */ - for (i = 0; i < n; i++) - if (locks[i]) { - delnames[delnum++] = locks[i]->ref_name; - ret |= delete_ref_loose(locks[i], types[i]); + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if (update->lock) { + ret |= delete_ref_loose(update->lock, update->type); + if (!(update->flags & REF_ISPRUNING)) + delnames[delnum++] = update->lock->ref_name; } - ret |= repack_without_refs(delnames, delnum); + } + + ret |= repack_without_refs(delnames, delnum, err); for (i = 0; i < delnum; i++) unlink_or_warn(git_path("logs/%s", delnames[i])); clear_loose_ref_cache(&ref_cache); cleanup: + transaction->state = REF_TRANSACTION_CLOSED; + for (i = 0; i < n; i++) - if (locks[i]) - unlock_ref(locks[i]); - free(updates); - free(types); - free(locks); + if (updates[i]->lock) + unlock_ref(updates[i]->lock); free(delnames); return ret; } |