summaryrefslogtreecommitdiff
path: root/refs/packed-backend.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs/packed-backend.c')
-rw-r--r--refs/packed-backend.c581
1 files changed, 478 insertions, 103 deletions
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..7fd73a0e6d 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -299,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -321,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -493,8 +500,49 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
+static int allocate_snapshot_buffer(struct snapshot *snapshot, int fd, struct stat *st)
+{
+ ssize_t bytes_read;
+ size_t size;
+
+ size = xsize_t(st->st_size);
+ if (!size)
+ return 0;
+
+ if (mmap_strategy == MMAP_NONE || size <= SMALL_FILE_SIZE) {
+ snapshot->buf = xmalloc(size);
+ bytes_read = read_in_full(fd, snapshot->buf, size);
+ if (bytes_read < 0 || bytes_read != size)
+ die_errno("couldn't read %s", snapshot->refs->path);
+ snapshot->mmapped = 0;
+ } else {
+ snapshot->buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ snapshot->mmapped = 1;
+ }
+
+ snapshot->start = snapshot->buf;
+ snapshot->eof = snapshot->buf + size;
+
+ return 1;
+}
+
/*
* Depending on `mmap_strategy`, either mmap or read the contents of
* the `packed-refs` file into the snapshot. Return 1 if the file
@@ -503,10 +551,9 @@ static void verify_buffer_safe(struct snapshot *snapshot)
*/
static int load_contents(struct snapshot *snapshot)
{
- int fd;
struct stat st;
- size_t size;
- ssize_t bytes_read;
+ int ret;
+ int fd;
fd = open(snapshot->refs->path, O_RDONLY);
if (fd < 0) {
@@ -528,27 +575,11 @@ static int load_contents(struct snapshot *snapshot)
if (fstat(fd, &st) < 0)
die_errno("couldn't stat %s", snapshot->refs->path);
- size = xsize_t(st.st_size);
-
- if (!size) {
- close(fd);
- return 0;
- } else if (mmap_strategy == MMAP_NONE || size <= SMALL_FILE_SIZE) {
- snapshot->buf = xmalloc(size);
- bytes_read = read_in_full(fd, snapshot->buf, size);
- if (bytes_read < 0 || bytes_read != size)
- die_errno("couldn't read %s", snapshot->refs->path);
- snapshot->mmapped = 0;
- } else {
- snapshot->buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
- snapshot->mmapped = 1;
- }
- close(fd);
- snapshot->start = snapshot->buf;
- snapshot->eof = snapshot->buf + size;
+ ret = allocate_snapshot_buffer(snapshot, fd, &st);
- return 1;
+ close(fd);
+ return ret;
}
static const char *find_reference_location_1(struct snapshot *snapshot,
@@ -693,7 +724,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
@@ -819,6 +850,8 @@ struct packed_ref_iterator {
struct snapshot *snapshot;
+ char *prefix;
+
/* The current position in the snapshot's buffer: */
const char *pos;
@@ -841,11 +874,9 @@ struct packed_ref_iterator {
};
/*
- * Move the iterator to the next record in the snapshot, without
- * respect for whether the record is actually required by the current
- * iteration. Adjust the fields in `iter` and return `ITER_OK` or
- * `ITER_DONE`. This function does not free the iterator in the case
- * of `ITER_DONE`.
+ * Move the iterator to the next record in the snapshot. Adjust the fields in
+ * `iter` and return `ITER_OK` or `ITER_DONE`. This function does not free the
+ * iterator in the case of `ITER_DONE`.
*/
static int next_record(struct packed_ref_iterator *iter)
{
@@ -894,6 +925,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
@@ -942,6 +976,9 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
int ok;
while ((ok = next_record(iter)) == ITER_OK) {
+ const char *refname = iter->base.refname;
+ const char *prefix = iter->prefix;
+
if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
!is_per_worktree_ref(iter->base.refname))
continue;
@@ -951,15 +988,41 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
&iter->oid, iter->flags))
continue;
+ while (prefix && *prefix) {
+ if ((unsigned char)*refname < (unsigned char)*prefix)
+ BUG("packed-refs backend yielded reference preceding its prefix");
+ else if ((unsigned char)*refname > (unsigned char)*prefix)
+ return ITER_DONE;
+ prefix++;
+ refname++;
+ }
+
return ITER_OK;
}
- if (ref_iterator_abort(ref_iterator) != ITER_DONE)
- ok = ITER_ERROR;
-
return ok;
}
+static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,
+ const char *prefix)
+{
+ struct packed_ref_iterator *iter =
+ (struct packed_ref_iterator *)ref_iterator;
+ const char *start;
+
+ if (prefix && *prefix)
+ start = find_reference_location(iter->snapshot, prefix, 0);
+ else
+ start = iter->snapshot->start;
+
+ free(iter->prefix);
+ iter->prefix = xstrdup_or_null(prefix);
+ iter->pos = start;
+ iter->eof = iter->snapshot->eof;
+
+ return 0;
+}
+
static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@@ -976,23 +1039,21 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
}
}
-static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator)
+static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct packed_ref_iterator *iter =
(struct packed_ref_iterator *)ref_iterator;
- int ok = ITER_DONE;
-
strbuf_release(&iter->refname_buf);
free(iter->jump);
+ free(iter->prefix);
release_snapshot(iter->snapshot);
- base_ref_iterator_free(ref_iterator);
- return ok;
}
static struct ref_iterator_vtable packed_ref_iterator_vtable = {
.advance = packed_ref_iterator_advance,
+ .seek = packed_ref_iterator_seek,
.peel = packed_ref_iterator_peel,
- .abort = packed_ref_iterator_abort
+ .release = packed_ref_iterator_release,
};
static int jump_list_entry_cmp(const void *va, const void *vb)
@@ -1104,7 +1165,6 @@ static struct ref_iterator *packed_ref_iterator_begin(
{
struct packed_ref_store *refs;
struct snapshot *snapshot;
- const char *start;
struct packed_ref_iterator *iter;
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
@@ -1120,14 +1180,6 @@ static struct ref_iterator *packed_ref_iterator_begin(
*/
snapshot = get_snapshot(refs);
- if (prefix && *prefix)
- start = find_reference_location(snapshot, prefix, 0);
- else
- start = snapshot->start;
-
- if (start == snapshot->eof)
- return empty_ref_iterator_begin();
-
CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable);
@@ -1137,19 +1189,15 @@ static struct ref_iterator *packed_ref_iterator_begin(
iter->snapshot = snapshot;
acquire_snapshot(snapshot);
-
- iter->pos = start;
- iter->eof = snapshot->eof;
strbuf_init(&iter->refname_buf, 0);
-
iter->base.oid = &iter->oid;
-
iter->repo = ref_store->repo;
iter->flags = flags;
- if (prefix && *prefix)
- /* Stop iteration after we've gone *past* prefix: */
- ref_iterator = prefix_ref_iterator_begin(ref_iterator, prefix, 0);
+ if (packed_ref_iterator_seek(&iter->base, prefix) < 0) {
+ ref_iterator_free(&iter->base);
+ return NULL;
+ }
return ref_iterator;
}
@@ -1312,10 +1360,12 @@ static int packed_ref_store_remove_on_disk(struct ref_store *ref_store,
* The packfile must be locked before calling this function and will
* remain locked when it is done.
*/
-static int write_with_updates(struct packed_ref_store *refs,
- struct string_list *updates,
- struct strbuf *err)
+static enum ref_transaction_error write_with_updates(struct packed_ref_store *refs,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
{
+ enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
+ struct string_list *updates = &transaction->refnames;
struct ref_iterator *iter = NULL;
size_t i;
int ok;
@@ -1339,7 +1389,7 @@ static int write_with_updates(struct packed_ref_store *refs,
strbuf_addf(err, "unable to create file %s: %s",
sb.buf, strerror(errno));
strbuf_release(&sb);
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
strbuf_release(&sb);
@@ -1362,8 +1412,10 @@ static int write_with_updates(struct packed_ref_store *refs,
*/
iter = packed_ref_iterator_begin(&refs->base, "", NULL,
DO_FOR_EACH_INCLUDE_BROKEN);
- if ((ok = ref_iterator_advance(iter)) != ITER_OK)
+ if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
+ ref_iterator_free(iter);
iter = NULL;
+ }
i = 0;
@@ -1393,6 +1445,14 @@ static int write_with_updates(struct packed_ref_store *refs,
strbuf_addf(err, "cannot update ref '%s': "
"reference already exists",
update->refname);
+ ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
+
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+ continue;
+ }
+
goto error;
} else if (!oideq(&update->old_oid, iter->oid)) {
strbuf_addf(err, "cannot update ref '%s': "
@@ -1400,6 +1460,14 @@ static int write_with_updates(struct packed_ref_store *refs,
update->refname,
oid_to_hex(iter->oid),
oid_to_hex(&update->old_oid));
+ ret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
+
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+ continue;
+ }
+
goto error;
}
}
@@ -1411,8 +1479,10 @@ static int write_with_updates(struct packed_ref_store *refs,
* the iterator over the unneeded
* value.
*/
- if ((ok = ref_iterator_advance(iter)) != ITER_OK)
+ if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
+ ref_iterator_free(iter);
iter = NULL;
+ }
cmp = +1;
} else {
/*
@@ -1434,6 +1504,14 @@ static int write_with_updates(struct packed_ref_store *refs,
"reference is missing but expected %s",
update->refname,
oid_to_hex(&update->old_oid));
+ ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
+
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+ continue;
+ }
+
goto error;
}
}
@@ -1449,8 +1527,10 @@ static int write_with_updates(struct packed_ref_store *refs,
peel_error ? NULL : &peeled))
goto write_error;
- if ((ok = ref_iterator_advance(iter)) != ITER_OK)
+ if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
+ ref_iterator_free(iter);
iter = NULL;
+ }
} else if (is_null_oid(&update->new_oid)) {
/*
* The update wants to delete the reference,
@@ -1489,7 +1569,7 @@ static int write_with_updates(struct packed_ref_store *refs,
strerror(errno));
strbuf_release(&sb);
delete_tempfile(&refs->tempfile);
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
return 0;
@@ -1497,13 +1577,12 @@ static int write_with_updates(struct packed_ref_store *refs,
write_error:
strbuf_addf(err, "error writing to %s: %s",
get_tempfile_path(refs->tempfile), strerror(errno));
+ ret = REF_TRANSACTION_ERROR_GENERIC;
error:
- if (iter)
- ref_iterator_abort(iter);
-
+ ref_iterator_free(iter);
delete_tempfile(&refs->tempfile);
- return -1;
+ return ret;
}
int is_packed_transaction_needed(struct ref_store *ref_store,
@@ -1604,8 +1683,6 @@ int is_packed_transaction_needed(struct ref_store *ref_store,
struct packed_transaction_backend_data {
/* True iff the transaction owns the packed-refs lock. */
int own_lock;
-
- struct string_list updates;
};
static void packed_transaction_cleanup(struct packed_ref_store *refs,
@@ -1614,8 +1691,6 @@ static void packed_transaction_cleanup(struct packed_ref_store *refs,
struct packed_transaction_backend_data *data = transaction->backend_data;
if (data) {
- string_list_clear(&data->updates, 0);
-
if (is_tempfile_active(refs->tempfile))
delete_tempfile(&refs->tempfile);
@@ -1640,8 +1715,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
"ref_transaction_prepare");
struct packed_transaction_backend_data *data;
- size_t i;
- int ret = TRANSACTION_GENERIC_ERROR;
+ enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
/*
* Note that we *don't* skip transactions with zero updates,
@@ -1653,34 +1727,17 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
*/
CALLOC_ARRAY(data, 1);
- string_list_init_nodup(&data->updates);
transaction->backend_data = data;
- /*
- * Stick the updates in a string list by refname so that we
- * can sort them:
- */
- for (i = 0; i < transaction->nr; i++) {
- struct ref_update *update = transaction->updates[i];
- struct string_list_item *item =
- string_list_append(&data->updates, update->refname);
-
- /* Store a pointer to update in item->util: */
- item->util = update;
- }
- string_list_sort(&data->updates);
-
- if (ref_update_reject_duplicates(&data->updates, err))
- goto failure;
-
if (!is_lock_file_locked(&refs->lock)) {
if (packed_refs_lock(ref_store, 0, err))
goto failure;
data->own_lock = 1;
}
- if (write_with_updates(refs, &data->updates, err))
+ ret = write_with_updates(refs, transaction, err);
+ if (ret)
goto failure;
transaction->state = REF_TRANSACTION_PREPARED;
@@ -1712,7 +1769,7 @@ static int packed_transaction_finish(struct ref_store *ref_store,
ref_store,
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
"ref_transaction_finish");
- int ret = TRANSACTION_GENERIC_ERROR;
+ int ret = REF_TRANSACTION_ERROR_GENERIC;
char *packed_refs_path;
clear_snapshot(refs);
@@ -1748,15 +1805,333 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol,
+ unsigned int *sorted)
+{
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
+}
+
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned int *sorted,
+ const char *start, const char *eof)
+{
+ struct strbuf refname = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
+ return ret;
+}
+
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct snapshot snapshot = { 0 };
+ unsigned int sorted = 0;
+ struct stat st;
+ int ret = 0;
+ int fd = -1;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file but a symlink");
+ goto cleanup;
+ }
+
+ ret = error_errno(_("unable to open '%s'"), refs->path);
+ goto cleanup;
+ } else if (fstat(fd, &st) < 0) {
+ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
+ } else if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+ if (!allocate_snapshot_buffer(&snapshot, fd, &st)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_EMPTY_PACKED_REFS_FILE,
+ "file is empty");
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, snapshot.start,
+ snapshot.eof);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, snapshot.start,
+ snapshot.eof);
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ clear_snapshot_buffer(&snapshot);
+ return ret;
}
struct ref_storage_be refs_be_packed = {