/* * The backend-independent part of the reference module. */ #include "cache.h" #include "lockfile.h" #include "refs.h" #include "refs/refs-internal.h" #include "object.h" #include "tag.h" /* * 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, and * ":", "?", "[", "\", "^", "~", SP, or TAB * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set */ 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, 5, 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 }; /* * 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 * - it has ASCII control characters, or * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or * - it ends with a "/", or * - it ends with ".lock", or * - it contains a "@{" portion */ static int check_refname_component(const char *refname, int *flags) { const char *cp; char last = '\0'; for (cp = refname; ; cp++) { 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; case 3: if (last == '@') return -1; /* Refname contains "@{". */ break; case 4: return -1; case 5: if (!(*flags & REFNAME_REFSPEC_PATTERN)) return -1; /* refspec can't be a pattern */ /* * Unset the pattern flag so that we only accept * a single asterisk for one side of refspec. */ *flags &= ~ REFNAME_REFSPEC_PATTERN; break; } last = ch; } out: if (cp == refname) return 0; /* Component has zero length. */ if (refname[0] == '.') return -1; /* Component starts with '.'. */ if (cp - refname >= LOCK_SUFFIX_LEN && !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN)) return -1; /* Refname ends with ".lock". */ return cp - refname; } int check_refname_format(const char *refname, int flags) { int component_len, component_count = 0; if (!strcmp(refname, "@")) /* Refname is a single character '@'. */ return -1; while (1) { /* We are at the start of a path component. */ component_len = check_refname_component(refname, &flags); if (component_len <= 0) return -1; component_count++; if (refname[component_len] == '\0') break; /* Skip to next component. */ refname += component_len + 1; } if (refname[component_len - 1] == '.') return -1; /* Refname ends with '.'. */ if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2) return -1; /* Refname has only one component. */ return 0; } int refname_is_safe(const char *refname) { if (starts_with(refname, "refs/")) { char *buf; int result; buf = xmalloc(strlen(refname) + 1); /* * Does the refname try to escape refs/? * For example: refs/foo/../bar is safe but refs/foo/../../bar * is not. */ result = !normalize_path_copy(buf, refname + strlen("refs/")); free(buf); return result; } while (*refname) { if (!isupper(*refname) && *refname != '_') return 0; refname++; } return 1; } char *resolve_refdup(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags, sha1, flags)); } /* The argument to filter_refs */ struct ref_filter { const char *pattern; each_ref_fn *fn; void *cb_data; }; int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags)) return 0; return -1; } int read_ref(const char *refname, unsigned char *sha1) { return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL); } int ref_exists(const char *refname) { unsigned char sha1[20]; return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL); } static int filter_refs(const char *refname, const struct object_id *oid, int flags, void *data) { struct ref_filter *filter = (struct ref_filter *)data; if (wildmatch(filter->pattern, refname, 0, NULL)) return 0; return filter->fn(refname, oid, flags, filter->cb_data); } enum peel_status peel_object(const unsigned char *name, unsigned char *sha1) { struct object *o = lookup_unknown_object(name); if (o->type == OBJ_NONE) { int type = sha1_object_info(name, NULL); if (type < 0 || !object_as_type(o, type, 0)) return PEEL_INVALID; } if (o->type != OBJ_TAG) return PEEL_NON_TAG; o = deref_tag_noverify(o); if (!o) return PEEL_INVALID; hashcpy(sha1, o->oid.hash); return PEEL_PEELED; } struct warn_if_dangling_data { FILE *fp; const char *refname; const struct string_list *refnames; const char *msg_fmt; }; static int warn_if_dangling_symref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct warn_if_dangling_data *d = cb_data; const char *resolves_to; struct object_id junk; if (!(flags & REF_ISSYMREF)) return 0; resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL); if (!resolves_to || (d->refname ? strcmp(resolves_to, d->refname) : !string_list_has_string(d->refnames, resolves_to))) { return 0; } fprintf(d->fp, d->msg_fmt, refname); fputc('\n', d->fp); return 0; } void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) { struct warn_if_dangling_data data; data.fp = fp; data.refname = refname; data.refnames = NULL; data.msg_fmt = msg_fmt; for_each_rawref(warn_if_dangling_symref, &data); } void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames) { struct warn_if_dangling_data data; data.fp = fp; data.refname = NULL; data.refnames = refnames; data.msg_fmt = msg_fmt; for_each_rawref(warn_if_dangling_symref, &data); } int for_each_tag_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/tags/", fn, cb_data); } int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) { return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data); } int for_each_branch_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/heads/", fn, cb_data); } int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) { return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data); } int for_each_remote_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/remotes/", fn, cb_data); } int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) { return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data); } int head_ref_namespaced(each_ref_fn fn, void *cb_data) { struct strbuf buf = STRBUF_INIT; int ret = 0; struct object_id oid; int flag; strbuf_addf(&buf, "%sHEAD", get_git_namespace()); if (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag)) ret = fn(buf.buf, &oid, flag, cb_data); strbuf_release(&buf); return ret; } int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data) { struct strbuf real_pattern = STRBUF_INIT; struct ref_filter filter; int ret; if (!prefix && !starts_with(pattern, "refs/")) strbuf_addstr(&real_pattern, "refs/"); else if (prefix) strbuf_addstr(&real_pattern, prefix); strbuf_addstr(&real_pattern, pattern); if (!has_glob_specials(pattern)) { /* Append implied '/' '*' if not present. */ strbuf_complete(&real_pattern, '/'); /* No need to check for '*', there is none. */ strbuf_addch(&real_pattern, '*'); } filter.pattern = real_pattern.buf; filter.fn = fn; filter.cb_data = cb_data; ret = for_each_ref(filter_refs, &filter); strbuf_release(&real_pattern); return ret; } int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) { return for_each_glob_ref_in(fn, pattern, NULL, cb_data); } const char *prettify_refname(const char *name) { return name + ( starts_with(name, "refs/heads/") ? 11 : starts_with(name, "refs/tags/") ? 10 : starts_with(name, "refs/remotes/") ? 13 : 0); } static const char *ref_rev_parse_rules[] = { "%.*s", "refs/%.*s", "refs/tags/%.*s", "refs/heads/%.*s", "refs/remotes/%.*s", "refs/remotes/%.*s/HEAD", NULL }; int refname_match(const char *abbrev_name, const char *full_name) { const char **p; const int abbrev_name_len = strlen(abbrev_name); for (p = ref_rev_parse_rules; *p; p++) { if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) { return 1; } } return 0; } /* * *string and *len will only be substituted, and *string returned (for * later free()ing) if the string passed in is a magic short-hand form * to name a branch. */ static char *substitute_branch_name(const char **string, int *len) { struct strbuf buf = STRBUF_INIT; int ret = interpret_branch_name(*string, *len, &buf); if (ret == *len) { size_t size; *string = strbuf_detach(&buf, &size); *len = size; return (char *)*string; } return NULL; } int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { char *last_branch = substitute_branch_name(&str, &len); const char **p, *r; int refs_found = 0; *ref = NULL; for (p = ref_rev_parse_rules; *p; p++) { char fullref[PATH_MAX]; unsigned char sha1_from_ref[20]; unsigned char *this_result; int flag; this_result = refs_found ? sha1_from_ref : sha1; mksnpath(fullref, sizeof(fullref), *p, len, str); r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING, this_result, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); if (!warn_ambiguous_refs) break; } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) { warning("ignoring dangling symref %s.", fullref); } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) { warning("ignoring broken ref %s.", fullref); } } free(last_branch); return refs_found; } int dwim_log(const char *str, int len, unsigned char *sha1, char **log) { char *last_branch = substitute_branch_name(&str, &len); const char **p; int logs_found = 0; *log = NULL; for (p = ref_rev_parse_rules; *p; p++) { unsigned char hash[20]; char path[PATH_MAX]; const char *ref, *it; mksnpath(path, sizeof(path), *p, len, str); ref = resolve_ref_unsafe(path, RESOLVE_REF_READING, hash, NULL); if (!ref) continue; if (reflog_exists(path)) it = path; else if (strcmp(ref, path) && reflog_exists(ref)) it = ref; else continue; if (!logs_found++) { *log = xstrdup(it); hashcpy(sha1, hash); } if (!warn_ambiguous_refs) break; } free(last_branch); return logs_found; } static int is_per_worktree_ref(const char *refname) { return !strcmp(refname, "HEAD") || starts_with(refname, "refs/bisect/"); } static int is_pseudoref_syntax(const char *refname) { const char *c; for (c = refname; *c; c++) { if (!isupper(*c) && *c != '-' && *c != '_') return 0; } return 1; } enum ref_type ref_type(const char *refname) { if (is_per_worktree_ref(refname)) return REF_TYPE_PER_WORKTREE; if (is_pseudoref_syntax(refname)) return REF_TYPE_PSEUDOREF; return REF_TYPE_NORMAL; } static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, const unsigned char *old_sha1, struct strbuf *err) { const char *filename; int fd; static struct lock_file lock; struct strbuf buf = STRBUF_INIT; int ret = -1; strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); filename = git_path("%s", pseudoref); fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); if (fd < 0) { strbuf_addf(err, "Could not open '%s' for writing: %s", filename, strerror(errno)); return -1; } if (old_sha1) { unsigned char actual_old_sha1[20]; if (read_ref(pseudoref, actual_old_sha1)) die("could not read ref '%s'", pseudoref); if (hashcmp(actual_old_sha1, old_sha1)) { strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref); rollback_lock_file(&lock); goto done; } } if (write_in_full(fd, buf.buf, buf.len) != buf.len) { strbuf_addf(err, "Could not write to '%s'", filename); rollback_lock_file(&lock); goto done; } commit_lock_file(&lock); ret = 0; done: strbuf_release(&buf); return ret; } static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1) { static struct lock_file lock; const char *filename; filename = git_path("%s", pseudoref); if (old_sha1 && !is_null_sha1(old_sha1)) { int fd; unsigned char actual_old_sha1[20]; fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); if (read_ref(pseudoref, actual_old_sha1)) die("could not read ref '%s'", pseudoref); if (hashcmp(actual_old_sha1, old_sha1)) { warning("Unexpected sha1 when deleting %s", pseudoref); rollback_lock_file(&lock); return -1; } unlink(filename); rollback_lock_file(&lock); } else { unlink(filename); } return 0; } int delete_ref(const char *refname, const unsigned char *old_sha1, unsigned int flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; if (ref_type(refname) == REF_TYPE_PSEUDOREF) return delete_pseudoref(refname, old_sha1); transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, refname, old_sha1, flags, NULL, &err) || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); return 1; } ref_transaction_free(transaction); strbuf_release(&err); return 0; } int copy_reflog_msg(char *buf, const char *msg) { char *cp = buf; char c; int wasspace = 1; *cp++ = '\t'; while ((c = *msg++)) { if (wasspace && isspace(c)) continue; wasspace = isspace(c); if (wasspace) c = ' '; *cp++ = c; } while (buf < cp && isspace(cp[-1])) cp--; *cp++ = '\n'; return cp - buf; } int should_autocreate_reflog(const char *refname) { if (!log_all_ref_updates) return 0; return starts_with(refname, "refs/heads/") || starts_with(refname, "refs/remotes/") || starts_with(refname, "refs/notes/") || !strcmp(refname, "HEAD"); } int is_branch(const char *refname) { return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); } 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_MODE(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_MODE(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 int flags, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt) { struct read_ref_at_cb cb; 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) { if (flags & GET_SHA1_QUIETLY) exit(128); else die("Log for %s is empty.", refname); } if (cb.found_it) return 0; for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb); return 1; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { assert(err); return xcalloc(1, sizeof(struct ref_transaction)); } void ref_transaction_free(struct ref_transaction *transaction) { int i; if (!transaction) return; for (i = 0; i < transaction->nr; i++) { free(transaction->updates[i]->msg); 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) + 1; struct ref_update *update = xcalloc(1, sizeof(*update) + len); memcpy((char *)update->refname, refname, len); /* includes NUL */ 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, unsigned int flags, const char *msg, struct strbuf *err) { struct ref_update *update; assert(err); if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: update called for transaction that is not open"); if (new_sha1 && !is_null_sha1(new_sha1) && check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { strbuf_addf(err, "refusing to update ref with bad name %s", refname); return -1; } update = add_update(transaction, refname); if (new_sha1) { hashcpy(update->new_sha1, new_sha1); flags |= REF_HAVE_NEW; } if (old_sha1) { hashcpy(update->old_sha1, old_sha1); flags |= REF_HAVE_OLD; } update->flags = flags; if (msg) update->msg = xstrdup(msg); return 0; } int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, unsigned int flags, const char *msg, struct strbuf *err) { if (!new_sha1 || is_null_sha1(new_sha1)) die("BUG: create called without valid new_sha1"); return ref_transaction_update(transaction, refname, new_sha1, null_sha1, flags, msg, err); } int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, unsigned int flags, const char *msg, struct strbuf *err) { if (old_sha1 && is_null_sha1(old_sha1)) die("BUG: delete called with old_sha1 set to zeros"); return ref_transaction_update(transaction, refname, null_sha1, old_sha1, flags, msg, err); } int ref_transaction_verify(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, unsigned int flags, struct strbuf *err) { if (!old_sha1) die("BUG: verify called with old_sha1 set to NULL"); return ref_transaction_update(transaction, refname, NULL, old_sha1, flags, NULL, err); } int update_ref(const char *msg, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, unsigned int flags, enum action_on_err onerr) { struct ref_transaction *t = NULL; struct strbuf err = STRBUF_INIT; int ret = 0; if (ref_type(refname) == REF_TYPE_PSEUDOREF) { ret = write_pseudoref(refname, new_sha1, old_sha1, &err); } else { t = ref_transaction_begin(&err); if (!t || ref_transaction_update(t, refname, new_sha1, old_sha1, flags, msg, &err) || ref_transaction_commit(t, &err)) { ret = 1; ref_transaction_free(t); } } if (ret) { const char *str = "update_ref failed for ref '%s': %s"; 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; } strbuf_release(&err); if (t) ref_transaction_free(t); return 0; } char *shorten_unambiguous_ref(const char *refname, int strict) { int i; static char **scanf_fmts; static int nr_rules; char *short_name; if (!nr_rules) { /* * Pre-generate scanf formats from ref_rev_parse_rules[]. * Generate a format suitable for scanf from a * ref_rev_parse_rules rule by interpolating "%s" at the * location of the "%.*s". */ size_t total_len = 0; size_t offset = 0; /* the rule list is NULL terminated, count them first */ for (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++) /* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */ total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1; scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len); offset = 0; for (i = 0; i < nr_rules; i++) { assert(offset < total_len); scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset; offset += snprintf(scanf_fmts[i], total_len - offset, ref_rev_parse_rules[i], 2, "%s") + 1; } } /* bail out if there are no rules */ if (!nr_rules) return xstrdup(refname); /* buffer for scanf result, at most refname must fit */ short_name = xstrdup(refname); /* skip first rule, it will always match */ for (i = nr_rules - 1; i > 0 ; --i) { int j; int rules_to_fail = i; int short_name_len; if (1 != sscanf(refname, scanf_fmts[i], short_name)) continue; short_name_len = strlen(short_name); /* * in strict mode, all (except the matched one) rules * must fail to resolve to a valid non-ambiguous ref */ if (strict) rules_to_fail = nr_rules; /* * check if the short name resolves to a valid ref, * but use only rules prior to the matched one */ for (j = 0; j < rules_to_fail; j++) { const char *rule = ref_rev_parse_rules[j]; char refname[PATH_MAX]; /* skip matched rule */ if (i == j) continue; /* * the short name is ambiguous, if it resolves * (with this previous rule) to a valid ref * read_ref() returns 0 on success */ mksnpath(refname, sizeof(refname), rule, short_name_len, short_name); if (ref_exists(refname)) break; } /* * short name is non-ambiguous if all previous rules * haven't resolved to a valid ref */ if (j == rules_to_fail) return short_name; } free(short_name); return xstrdup(refname); } static struct string_list *hide_refs; int parse_hide_refs_config(const char *var, const char *value, const char *section) { if (!strcmp("transfer.hiderefs", var) || /* NEEDSWORK: use parse_config_key() once both are merged */ (starts_with(var, section) && var[strlen(section)] == '.' && !strcmp(var + strlen(section), ".hiderefs"))) { char *ref; int len; if (!value) return config_error_nonbool(var); ref = xstrdup(value); len = strlen(ref); while (len && ref[len - 1] == '/') ref[--len] = '\0'; if (!hide_refs) { hide_refs = xcalloc(1, sizeof(*hide_refs)); hide_refs->strdup_strings = 1; } string_list_append(hide_refs, ref); } return 0; } int ref_is_hidden(const char *refname, const char *refname_full) { int i; if (!hide_refs) return 0; for (i = hide_refs->nr - 1; i >= 0; i--) { const char *match = hide_refs->items[i].string; const char *subject; int neg = 0; int len; if (*match == '!') { neg = 1; match++; } if (*match == '^') { subject = refname_full; match++; } else { subject = refname; } /* refname can be NULL when namespaces are used. */ if (!subject || !starts_with(subject, match)) continue; len = strlen(match); if (!subject[len] || subject[len] == '/') return !neg; } return 0; } const char *find_descendant_ref(const char *dirname, const struct string_list *extras, const struct string_list *skip) { int pos; if (!extras) return NULL; /* * Look at the place where dirname would be inserted into * extras. If there is an entry at that position that starts * with dirname (remember, dirname includes the trailing * slash) and is not in skip, then we have a conflict. */ for (pos = string_list_find_insert_index(extras, dirname, 0); pos < extras->nr; pos++) { const char *extra_refname = extras->items[pos].string; if (!starts_with(extra_refname, dirname)) break; if (!skip || !string_list_has_string(skip, extra_refname)) return extra_refname; } return NULL; } int rename_ref_available(const char *oldname, const char *newname) { struct string_list skip = STRING_LIST_INIT_NODUP; struct strbuf err = STRBUF_INIT; int ret; string_list_insert(&skip, oldname); ret = !verify_refname_available(newname, NULL, &skip, &err); if (!ret) error("%s", err.buf); string_list_clear(&skip, 0); strbuf_release(&err); return ret; }