summaryrefslogtreecommitdiff
path: root/refs.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c290
1 files changed, 241 insertions, 49 deletions
diff --git a/refs.c b/refs.c
index 33ced65a78..3e73a0a36d 100644
--- a/refs.c
+++ b/refs.c
@@ -275,10 +275,8 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
list = get_ref_dir(ref, list);
continue;
}
- if (!resolve_ref(ref, sha1, 1, &flag)) {
- error("%s points nowhere!", ref);
- continue;
- }
+ if (!resolve_ref(ref, sha1, 1, &flag))
+ hashclr(sha1);
list = add_ref(ref, sha1, flag, list, NULL);
}
free(ref);
@@ -287,6 +285,36 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
return sort_ref_list(list);
}
+struct warn_if_dangling_data {
+ FILE *fp;
+ const char *refname;
+ const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct warn_if_dangling_data *d = cb_data;
+ const char *resolves_to;
+ unsigned char junk[20];
+
+ if (!(flags & REF_ISSYMREF))
+ return 0;
+
+ resolves_to = resolve_ref(refname, junk, 0, NULL);
+ if (!resolves_to || strcmp(resolves_to, d->refname))
+ return 0;
+
+ fprintf(d->fp, d->msg_fmt, refname);
+ return 0;
+}
+
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+{
+ struct warn_if_dangling_data data = { fp, refname, msg_fmt };
+ for_each_rawref(warn_if_dangling_symref, &data);
+}
+
static struct ref_list *get_loose_refs(void)
{
if (!cached_refs.did_loose) {
@@ -498,16 +526,20 @@ int read_ref(const char *ref, unsigned char *sha1)
return -1;
}
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
static int do_one_ref(const char *base, each_ref_fn fn, int trim,
- void *cb_data, struct ref_list *entry)
+ int flags, void *cb_data, struct ref_list *entry)
{
if (strncmp(base, entry->name, trim))
return 0;
+ /* Is this a "negative ref" that represents a deleted ref? */
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;
+ if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (!has_sha1_file(entry->sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
+ }
}
current_ref = entry;
return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
@@ -561,7 +593,7 @@ fallback:
}
static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
- void *cb_data)
+ int flags, void *cb_data)
{
int retval = 0;
struct ref_list *packed = get_packed_refs();
@@ -570,7 +602,7 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
struct ref_list *extra;
for (extra = extra_refs; extra; extra = extra->next)
- retval = do_one_ref(base, fn, trim, cb_data, extra);
+ retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
while (packed && loose) {
struct ref_list *entry;
@@ -586,13 +618,13 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
entry = packed;
packed = packed->next;
}
- retval = do_one_ref(base, fn, trim, cb_data, entry);
+ retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
if (retval)
goto end_each;
}
for (packed = packed ? packed : loose; packed; packed = packed->next) {
- retval = do_one_ref(base, fn, trim, cb_data, packed);
+ retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
if (retval)
goto end_each;
}
@@ -614,22 +646,38 @@ int head_ref(each_ref_fn fn, void *cb_data)
int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/", fn, 0, cb_data);
+ return do_for_each_ref("refs/", fn, 0, 0, cb_data);
+}
+
+int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/tags/", fn, 10, cb_data);
+ return for_each_ref_in("refs/tags/", fn, cb_data);
}
int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/heads/", fn, 11, cb_data);
+ return for_each_ref_in("refs/heads/", fn, cb_data);
}
int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
+ return for_each_ref_in("refs/remotes/", fn, cb_data);
+}
+
+int for_each_replace_ref(each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
+}
+
+int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref("refs/", fn, 0,
+ DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
/*
@@ -640,12 +688,14 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data)
* - it has double dots "..", or
* - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
* - it ends with a "/".
+ * - it ends with ".lock"
+ * - it contains a "\" (backslash)
*/
static inline int bad_ref_char(int ch)
{
if (((unsigned) ch) <= ' ' ||
- ch == '~' || ch == '^' || ch == ':')
+ ch == '~' || ch == '^' || ch == ':' || ch == '\\')
return 1;
/* 2.13 Pattern Matching Notation */
if (ch == '?' || ch == '[') /* Unsupported */
@@ -657,7 +707,8 @@ static inline int bad_ref_char(int ch)
int check_ref_format(const char *ref)
{
- int ch, level, bad_type;
+ int ch, level, bad_type, last;
+ int ret = CHECK_REF_FORMAT_OK;
const char *cp = ref;
level = 0;
@@ -673,33 +724,49 @@ int check_ref_format(const char *ref)
return CHECK_REF_FORMAT_ERROR;
bad_type = bad_ref_char(ch);
if (bad_type) {
- return (bad_type == 2 && !*cp)
- ? CHECK_REF_FORMAT_WILDCARD
- : CHECK_REF_FORMAT_ERROR;
+ if (bad_type == 2 && (!*cp || *cp == '/') &&
+ ret == CHECK_REF_FORMAT_OK)
+ ret = CHECK_REF_FORMAT_WILDCARD;
+ else
+ return CHECK_REF_FORMAT_ERROR;
}
+ last = ch;
/* scan the rest of the path component */
while ((ch = *cp++) != 0) {
bad_type = bad_ref_char(ch);
- if (bad_type) {
- return (bad_type == 2 && !*cp)
- ? CHECK_REF_FORMAT_WILDCARD
- : CHECK_REF_FORMAT_ERROR;
- }
+ if (bad_type)
+ return CHECK_REF_FORMAT_ERROR;
if (ch == '/')
break;
- if (ch == '.' && *cp == '.')
+ if (last == '.' && ch == '.')
+ return CHECK_REF_FORMAT_ERROR;
+ if (last == '@' && ch == '{')
return CHECK_REF_FORMAT_ERROR;
+ last = ch;
}
level++;
if (!ch) {
+ if (ref <= cp - 2 && cp[-2] == '.')
+ return CHECK_REF_FORMAT_ERROR;
if (level < 2)
return CHECK_REF_FORMAT_ONELEVEL;
- return CHECK_REF_FORMAT_OK;
+ if (has_extension(ref, ".lock"))
+ return CHECK_REF_FORMAT_ERROR;
+ return ret;
}
}
}
+const char *prettify_refname(const char *name)
+{
+ return name + (
+ !prefixcmp(name, "refs/heads/") ? 11 :
+ !prefixcmp(name, "refs/tags/") ? 10 :
+ !prefixcmp(name, "refs/remotes/") ? 13 :
+ 0);
+}
+
const char *ref_rev_parse_rules[] = {
"%.*s",
"refs/%.*s",
@@ -760,7 +827,7 @@ static int remove_empty_directories(const char *file)
strbuf_init(&path, 20);
strbuf_addstr(&path, file);
- result = remove_dir_recursively(&path, 1);
+ result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
strbuf_release(&path);
@@ -833,8 +900,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(ref, NULL, get_packed_refs(), 0))
+ !is_refname_available(ref, NULL, get_packed_refs(), 0)) {
+ last_errno = ENOTDIR;
goto error_return;
+ }
lock->lk = xcalloc(1, sizeof(struct lock_file));
@@ -904,8 +973,10 @@ static int repack_without_ref(const char *refname)
if (!found)
return 0;
fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
- if (fd < 0)
+ if (fd < 0) {
+ unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refname);
+ }
for (list = packed_ref_list; list; list = list->next) {
char line[PATH_MAX + 100];
@@ -942,12 +1013,10 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
} else {
path = git_path("%s", refname);
}
- err = unlink(path);
- if (err && errno != ENOENT) {
+ err = unlink_or_warn(path);
+ if (err && errno != ENOENT)
ret = 1;
- error("unlink(%s) failed: %s",
- path, strerror(errno));
- }
+
if (!(delopt & REF_NODEREF))
lock->lk->filename[i] = '.';
}
@@ -957,10 +1026,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
*/
ret |= repack_without_ref(refname);
- err = unlink(git_path("logs/%s", lock->ref_name));
- if (err && errno != ENOENT)
- fprintf(stderr, "warning: unlink(%s) failed: %s",
- git_path("logs/%s", lock->ref_name), strerror(errno));
+ unlink_or_warn(git_path("logs/%s", lock->ref_name));
invalidate_cached_refs();
unlock_ref(lock);
return ret;
@@ -1321,7 +1387,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
if (adjust_shared_perm(git_HEAD)) {
error("Unable to fix permissions on %s", lockpath);
error_unlink_return:
- unlink(lockpath);
+ unlink_or_warn(lockpath);
error_free_return:
free(git_HEAD);
return -1;
@@ -1361,7 +1427,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
logfile = git_path("logs/%s", ref);
logfd = open(logfile, O_RDONLY, 0);
if (logfd < 0)
- die("Unable to read log %s: %s", logfile, strerror(errno));
+ die_errno("Unable to read log '%s'", logfile);
fstat(logfd, &st);
if (!st.st_size)
die("Log %s is empty.", logfile);
@@ -1401,8 +1467,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
if (hashcmp(logged_sha1, sha1)) {
- fprintf(stderr,
- "warning: Log %s has gap after %s.\n",
+ warning("Log %s has gap after %s.",
logfile, show_date(date, tz, DATE_RFC2822));
}
}
@@ -1414,8 +1479,7 @@ 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)) {
- fprintf(stderr,
- "warning: Log %s unexpectedly ended on %s.\n",
+ warning("Log %s unexpectedly ended on %s.",
logfile, show_date(date, tz, DATE_RFC2822));
}
}
@@ -1453,7 +1517,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
return 1;
}
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
{
const char *logfile;
FILE *logfp;
@@ -1464,6 +1528,18 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
logfp = fopen(logfile, "r");
if (!logfp)
return -1;
+
+ if (ofs) {
+ struct stat statbuf;
+ if (fstat(fileno(logfp), &statbuf) ||
+ statbuf.st_size < ofs ||
+ fseek(logfp, -ofs, SEEK_END) ||
+ fgets(buf, sizeof(buf), logfp)) {
+ fclose(logfp);
+ return -1;
+ }
+ }
+
while (fgets(buf, sizeof(buf), logfp)) {
unsigned char osha1[20], nsha1[20];
char *email_end, *message;
@@ -1497,6 +1573,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
return ret;
}
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+ return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+}
+
static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
{
DIR *dir = opendir(git_path("logs/%s", base));
@@ -1577,10 +1658,121 @@ int update_ref(const char *action, const char *refname,
return 0;
}
-struct ref *find_ref_by_name(struct ref *list, const char *name)
+struct ref *find_ref_by_name(const struct ref *list, const char *name)
{
for ( ; list; list = list->next)
if (!strcmp(list->name, name))
- return list;
+ return (struct ref *)list;
return NULL;
}
+
+/*
+ * generate a format suitable for scanf from a ref_rev_parse_rules
+ * rule, that is replace the "%.*s" spec with a "%s" spec
+ */
+static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
+{
+ char *spec;
+
+ spec = strstr(rule, "%.*s");
+ if (!spec || strstr(spec + 4, "%.*s"))
+ die("invalid rule in ref_rev_parse_rules: %s", rule);
+
+ /* copy all until spec */
+ strncpy(scanf_fmt, rule, spec - rule);
+ scanf_fmt[spec - rule] = '\0';
+ /* copy new spec */
+ strcat(scanf_fmt, "%s");
+ /* copy remaining rule */
+ strcat(scanf_fmt, spec + 4);
+
+ return;
+}
+
+char *shorten_unambiguous_ref(const char *ref, int strict)
+{
+ int i;
+ static char **scanf_fmts;
+ static int nr_rules;
+ char *short_name;
+
+ /* pre generate scanf formats from ref_rev_parse_rules[] */
+ if (!nr_rules) {
+ size_t total_len = 0;
+
+ /* the rule list is NULL terminated, count them first */
+ for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
+ /* no +1 because strlen("%s") < strlen("%.*s") */
+ total_len += strlen(ref_rev_parse_rules[nr_rules]);
+
+ scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
+
+ total_len = 0;
+ for (i = 0; i < nr_rules; i++) {
+ scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+ + total_len;
+ gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
+ total_len += strlen(ref_rev_parse_rules[i]);
+ }
+ }
+
+ /* bail out if there are no rules */
+ if (!nr_rules)
+ return xstrdup(ref);
+
+ /* buffer for scanf result, at most ref must fit */
+ short_name = xstrdup(ref);
+
+ /* 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(ref, 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];
+ unsigned char short_objectname[20];
+ 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 (!read_ref(refname, short_objectname))
+ 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(ref);
+}