merge: add t7613 for submodule updates
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 728a76164830368637e95390d95ce3f7c7df659e..dc457742eade241ec6b0001580d1ce1a880fee31 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -6,8 +6,29 @@
 #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
+};
+
+/*
+ * 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 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] == '.') {
@@ -1611,6 +1622,7 @@ int peel_ref(const char *refname, unsigned char *sha1)
 struct warn_if_dangling_data {
        FILE *fp;
        const char *refname;
+       const struct string_list *refnames;
        const char *msg_fmt;
 };
 
@@ -1625,8 +1637,12 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
                return 0;
 
        resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
-       if (!resolves_to || strcmp(resolves_to, d->refname))
+       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);
@@ -1639,6 +1655,18 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
 
        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);
 }
@@ -1999,7 +2027,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;
@@ -2008,12 +2035,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;
@@ -2431,7 +2455,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
        return 0;
 }
 
-static int repack_without_refs(const char **refnames, int n)
+int repack_without_refs(const char **refnames, int n)
 {
        struct ref_dir *packed;
        struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
@@ -2930,122 +2954,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];