#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;
+ case 3:
+ 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 4:
+ return -1;
+ }
last = ch;
}
+out:
if (cp == refname)
return 0; /* Component has zero length. */
if (refname[0] == '.') {
struct warn_if_dangling_data {
FILE *fp;
const char *refname;
+ const struct string_list *refnames;
const char *msg_fmt;
};
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);
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);
}
*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;
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;
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;
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];
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;
+ case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+ case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+ case UPDATE_REFS_QUIET_ON_ERR: break;
}
}
return lock;
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;
+ case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+ case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+ case UPDATE_REFS_QUIET_ON_ERR: break;
}
return 1;
}
return 0;
}
+/**
+ * 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;
+ int type;
+ const char refname[FLEX_ARRAY];
+};
+
+/*
+ * 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;
+};
+
+struct ref_transaction *ref_transaction_begin(void)
+{
+ return xcalloc(1, sizeof(struct ref_transaction));
+}
+
+static void ref_transaction_free(struct ref_transaction *transaction)
+{
+ int i;
+
+ for (i = 0; i < transaction->nr; i++)
+ free(transaction->updates[i]);
+
+ free(transaction->updates);
+ free(transaction);
+}
+
+void ref_transaction_rollback(struct ref_transaction *transaction)
+{
+ ref_transaction_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;
+}
+
+void ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *new_sha1, unsigned char *old_sha1,
+ int flags, int have_old)
+{
+ struct ref_update *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);
+}
+
+void ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *new_sha1,
+ int flags)
+{
+ struct ref_update *update = add_update(transaction, refname);
+
+ assert(!is_null_sha1(new_sha1));
+ hashcpy(update->new_sha1, new_sha1);
+ hashclr(update->old_sha1);
+ update->flags = flags;
+ update->have_old = 1;
+}
+
+void ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ unsigned char *old_sha1,
+ int flags, int have_old)
+{
+ struct ref_update *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);
+ }
+}
+
int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
{
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,
{
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:
+ case UPDATE_REFS_MSG_ON_ERR:
+ error(str, updates[i]->refname); break;
+ case UPDATE_REFS_DIE_ON_ERR:
+ die(str, updates[i]->refname); break;
+ case UPDATE_REFS_QUIET_ON_ERR:
break;
}
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, enum action_on_err onerr)
{
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 (!n)
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);
if (ret)
/* 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 = update_ref_lock(update->refname,
+ (update->have_old ?
+ update->old_sha1 : NULL),
+ update->flags,
+ &update->type, onerr);
+ if (!update->lock) {
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 */
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if (!is_null_sha1(update->new_sha1)) {
+ ret = update_ref_write(msg,
+ update->refname,
+ update->new_sha1,
+ update->lock, onerr);
+ update->lock = NULL; /* freed by update_ref_write */
if (ret)
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) {
+ delnames[delnum++] = update->lock->ref_name;
+ ret |= delete_ref_loose(update->lock, update->type);
}
+ }
+
ret |= repack_without_refs(delnames, delnum);
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
cleanup:
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);
+ ref_transaction_free(transaction);
return ret;
}