@@ -2470,6 +2470,7 @@ REFTABLE_TEST_OBJS += reftable/merged_test.o
REFTABLE_TEST_OBJS += reftable/pq_test.o
REFTABLE_TEST_OBJS += reftable/record_test.o
REFTABLE_TEST_OBJS += reftable/readwrite_test.o
+REFTABLE_TEST_OBJS += reftable/refname_test.o
REFTABLE_TEST_OBJS += reftable/test_framework.o
REFTABLE_TEST_OBJS += reftable/tree_test.o
new file mode 100644
@@ -0,0 +1,209 @@
+/*
+ Copyright 2020 Google LLC
+
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file or at
+ https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+#include "reftable-error.h"
+#include "basics.h"
+#include "refname.h"
+#include "reftable-iterator.h"
+
+struct find_arg {
+ char **names;
+ const char *want;
+};
+
+static int find_name(size_t k, void *arg)
+{
+ struct find_arg *f_arg = arg;
+ return strcmp(f_arg->names[k], f_arg->want) >= 0;
+}
+
+static int modification_has_ref(struct modification *mod, const char *name)
+{
+ struct reftable_ref_record ref = { NULL };
+ int err = 0;
+
+ if (mod->add_len > 0) {
+ struct find_arg arg = {
+ .names = mod->add,
+ .want = name,
+ };
+ int idx = binsearch(mod->add_len, find_name, &arg);
+ if (idx < mod->add_len && !strcmp(mod->add[idx], name)) {
+ return 0;
+ }
+ }
+
+ if (mod->del_len > 0) {
+ struct find_arg arg = {
+ .names = mod->del,
+ .want = name,
+ };
+ int idx = binsearch(mod->del_len, find_name, &arg);
+ if (idx < mod->del_len && !strcmp(mod->del[idx], name)) {
+ return 1;
+ }
+ }
+
+ err = reftable_table_read_ref(&mod->tab, name, &ref);
+ reftable_ref_record_release(&ref);
+ return err;
+}
+
+static void modification_release(struct modification *mod)
+{
+ /* don't delete the strings themselves; they're owned by ref records.
+ */
+ FREE_AND_NULL(mod->add);
+ FREE_AND_NULL(mod->del);
+ mod->add_len = 0;
+ mod->del_len = 0;
+}
+
+static int modification_has_ref_with_prefix(struct modification *mod,
+ const char *prefix)
+{
+ struct reftable_iterator it = { NULL };
+ struct reftable_ref_record ref = { NULL };
+ int err = 0;
+
+ if (mod->add_len > 0) {
+ struct find_arg arg = {
+ .names = mod->add,
+ .want = prefix,
+ };
+ int idx = binsearch(mod->add_len, find_name, &arg);
+ if (idx < mod->add_len &&
+ !strncmp(prefix, mod->add[idx], strlen(prefix)))
+ goto done;
+ }
+ err = reftable_table_seek_ref(&mod->tab, &it, prefix);
+ if (err)
+ goto done;
+
+ while (1) {
+ err = reftable_iterator_next_ref(&it, &ref);
+ if (err)
+ goto done;
+
+ if (mod->del_len > 0) {
+ struct find_arg arg = {
+ .names = mod->del,
+ .want = ref.refname,
+ };
+ int idx = binsearch(mod->del_len, find_name, &arg);
+ if (idx < mod->del_len &&
+ !strcmp(ref.refname, mod->del[idx])) {
+ continue;
+ }
+ }
+
+ if (strncmp(ref.refname, prefix, strlen(prefix))) {
+ err = 1;
+ goto done;
+ }
+ err = 0;
+ goto done;
+ }
+
+done:
+ reftable_ref_record_release(&ref);
+ reftable_iterator_destroy(&it);
+ return err;
+}
+
+static int validate_refname(const char *name)
+{
+ while (1) {
+ char *next = strchr(name, '/');
+ if (!*name) {
+ return REFTABLE_REFNAME_ERROR;
+ }
+ if (!next) {
+ return 0;
+ }
+ if (next - name == 0 || (next - name == 1 && *name == '.') ||
+ (next - name == 2 && name[0] == '.' && name[1] == '.'))
+ return REFTABLE_REFNAME_ERROR;
+ name = next + 1;
+ }
+ return 0;
+}
+
+int validate_ref_record_addition(struct reftable_table tab,
+ struct reftable_ref_record *recs, size_t sz)
+{
+ struct modification mod = {
+ .tab = tab,
+ .add = reftable_calloc(sizeof(char *) * sz),
+ .del = reftable_calloc(sizeof(char *) * sz),
+ };
+ int i = 0;
+ int err = 0;
+ for (; i < sz; i++) {
+ if (reftable_ref_record_is_deletion(&recs[i])) {
+ mod.del[mod.del_len++] = recs[i].refname;
+ } else {
+ mod.add[mod.add_len++] = recs[i].refname;
+ }
+ }
+
+ err = modification_validate(&mod);
+ modification_release(&mod);
+ return err;
+}
+
+static void strbuf_trim_component(struct strbuf *sl)
+{
+ while (sl->len > 0) {
+ int is_slash = (sl->buf[sl->len - 1] == '/');
+ strbuf_setlen(sl, sl->len - 1);
+ if (is_slash)
+ break;
+ }
+}
+
+int modification_validate(struct modification *mod)
+{
+ struct strbuf slashed = STRBUF_INIT;
+ int err = 0;
+ int i = 0;
+ for (; i < mod->add_len; i++) {
+ err = validate_refname(mod->add[i]);
+ if (err)
+ goto done;
+ strbuf_reset(&slashed);
+ strbuf_addstr(&slashed, mod->add[i]);
+ strbuf_addstr(&slashed, "/");
+
+ err = modification_has_ref_with_prefix(mod, slashed.buf);
+ if (err == 0) {
+ err = REFTABLE_NAME_CONFLICT;
+ goto done;
+ }
+ if (err < 0)
+ goto done;
+
+ strbuf_reset(&slashed);
+ strbuf_addstr(&slashed, mod->add[i]);
+ while (slashed.len) {
+ strbuf_trim_component(&slashed);
+ err = modification_has_ref(mod, slashed.buf);
+ if (err == 0) {
+ err = REFTABLE_NAME_CONFLICT;
+ goto done;
+ }
+ if (err < 0)
+ goto done;
+ }
+ }
+ err = 0;
+done:
+ strbuf_release(&slashed);
+ return err;
+}
new file mode 100644
@@ -0,0 +1,29 @@
+/*
+ Copyright 2020 Google LLC
+
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file or at
+ https://developers.google.com/open-source/licenses/bsd
+*/
+#ifndef REFNAME_H
+#define REFNAME_H
+
+#include "reftable-record.h"
+#include "reftable-generic.h"
+
+struct modification {
+ struct reftable_table tab;
+
+ char **add;
+ size_t add_len;
+
+ char **del;
+ size_t del_len;
+};
+
+int validate_ref_record_addition(struct reftable_table tab,
+ struct reftable_ref_record *recs, size_t sz);
+
+int modification_validate(struct modification *mod);
+
+#endif
new file mode 100644
@@ -0,0 +1,102 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"
+#include "block.h"
+#include "blocksource.h"
+#include "constants.h"
+#include "reader.h"
+#include "record.h"
+#include "refname.h"
+#include "reftable-error.h"
+#include "reftable-writer.h"
+#include "system.h"
+
+#include "test_framework.h"
+#include "reftable-tests.h"
+
+struct testcase {
+ char *add;
+ char *del;
+ int error_code;
+};
+
+static void test_conflict(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ struct reftable_ref_record rec = {
+ .refname = "a/b",
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = "destination", /* make sure it's not a symref.
+ */
+ .update_index = 1,
+ };
+ int err;
+ int i;
+ struct reftable_block_source source = { NULL };
+ struct reftable_reader *rd = NULL;
+ struct reftable_table tab = { NULL };
+ struct testcase cases[] = {
+ { "a/b/c", NULL, REFTABLE_NAME_CONFLICT },
+ { "b", NULL, 0 },
+ { "a", NULL, REFTABLE_NAME_CONFLICT },
+ { "a", "a/b", 0 },
+
+ { "p/", NULL, REFTABLE_REFNAME_ERROR },
+ { "p//q", NULL, REFTABLE_REFNAME_ERROR },
+ { "p/./q", NULL, REFTABLE_REFNAME_ERROR },
+ { "p/../q", NULL, REFTABLE_REFNAME_ERROR },
+
+ { "a/b/c", "a/b", 0 },
+ { NULL, "a//b", 0 },
+ };
+ reftable_writer_set_limits(w, 1, 1);
+
+ err = reftable_writer_add_ref(w, &rec);
+ EXPECT_ERR(err);
+
+ err = reftable_writer_close(w);
+ EXPECT_ERR(err);
+ reftable_writer_free(w);
+
+ block_source_from_strbuf(&source, &buf);
+ err = reftable_new_reader(&rd, &source, "filename");
+ EXPECT_ERR(err);
+
+ reftable_table_from_reader(&tab, rd);
+
+ for (i = 0; i < ARRAY_SIZE(cases); i++) {
+ struct modification mod = {
+ .tab = tab,
+ };
+
+ if (cases[i].add) {
+ mod.add = &cases[i].add;
+ mod.add_len = 1;
+ }
+ if (cases[i].del) {
+ mod.del = &cases[i].del;
+ mod.del_len = 1;
+ }
+
+ err = modification_validate(&mod);
+ EXPECT(err == cases[i].error_code);
+ }
+
+ reftable_reader_free(rd);
+ strbuf_release(&buf);
+}
+
+int refname_test_main(int argc, const char *argv[])
+{
+ RUN_TEST(test_conflict);
+ return 0;
+}
@@ -8,6 +8,7 @@ int cmd__reftable(int argc, const char **argv)
merged_test_main(argc, argv);
pq_test_main(argc, argv);
record_test_main(argc, argv);
+ refname_test_main(argc, argv);
readwrite_test_main(argc, argv);
tree_test_main(argc, argv);
return 0;