1315 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			1315 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
| /*
 | |
| 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
 | |
| */
 | |
| 
 | |
| /* record.c - methods for different types of records. */
 | |
| 
 | |
| #include "record.h"
 | |
| 
 | |
| #include "system.h"
 | |
| #include "constants.h"
 | |
| #include "reftable-error.h"
 | |
| #include "basics.h"
 | |
| 
 | |
| static struct reftable_record_vtable *
 | |
| reftable_record_vtable(struct reftable_record *rec);
 | |
| static void *reftable_record_data(struct reftable_record *rec);
 | |
| 
 | |
| int get_var_int(uint64_t *dest, struct string_view *in)
 | |
| {
 | |
| 	int ptr = 0;
 | |
| 	uint64_t val;
 | |
| 
 | |
| 	if (in->len == 0)
 | |
| 		return -1;
 | |
| 	val = in->buf[ptr] & 0x7f;
 | |
| 
 | |
| 	while (in->buf[ptr] & 0x80) {
 | |
| 		ptr++;
 | |
| 		if (ptr > in->len) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		val = (val + 1) << 7 | (uint64_t)(in->buf[ptr] & 0x7f);
 | |
| 	}
 | |
| 
 | |
| 	*dest = val;
 | |
| 	return ptr + 1;
 | |
| }
 | |
| 
 | |
| int put_var_int(struct string_view *dest, uint64_t val)
 | |
| {
 | |
| 	uint8_t buf[10] = { 0 };
 | |
| 	int i = 9;
 | |
| 	int n = 0;
 | |
| 	buf[i] = (uint8_t)(val & 0x7f);
 | |
| 	i--;
 | |
| 	while (1) {
 | |
| 		val >>= 7;
 | |
| 		if (!val) {
 | |
| 			break;
 | |
| 		}
 | |
| 		val--;
 | |
| 		buf[i] = 0x80 | (uint8_t)(val & 0x7f);
 | |
| 		i--;
 | |
| 	}
 | |
| 
 | |
| 	n = sizeof(buf) - i - 1;
 | |
| 	if (dest->len < n)
 | |
| 		return -1;
 | |
| 	memcpy(dest->buf, &buf[i + 1], n);
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| int reftable_is_block_type(uint8_t typ)
 | |
| {
 | |
| 	switch (typ) {
 | |
| 	case BLOCK_TYPE_REF:
 | |
| 	case BLOCK_TYPE_LOG:
 | |
| 	case BLOCK_TYPE_OBJ:
 | |
| 	case BLOCK_TYPE_INDEX:
 | |
| 		return 1;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
 | |
| {
 | |
| 	switch (rec->value_type) {
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		return rec->value.val1;
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		return rec->value.val2.value;
 | |
| 	default:
 | |
| 		return NULL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec)
 | |
| {
 | |
| 	switch (rec->value_type) {
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		return rec->value.val2.target_value;
 | |
| 	default:
 | |
| 		return NULL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int decode_string(struct strbuf *dest, struct string_view in)
 | |
| {
 | |
| 	int start_len = in.len;
 | |
| 	uint64_t tsize = 0;
 | |
| 	int n = get_var_int(&tsize, &in);
 | |
| 	if (n <= 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&in, n);
 | |
| 	if (in.len < tsize)
 | |
| 		return -1;
 | |
| 
 | |
| 	strbuf_reset(dest);
 | |
| 	strbuf_add(dest, in.buf, tsize);
 | |
| 	string_view_consume(&in, tsize);
 | |
| 
 | |
| 	return start_len - in.len;
 | |
| }
 | |
| 
 | |
| static int encode_string(char *str, struct string_view s)
 | |
| {
 | |
| 	struct string_view start = s;
 | |
| 	int l = strlen(str);
 | |
| 	int n = put_var_int(&s, l);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 	if (s.len < l)
 | |
| 		return -1;
 | |
| 	memcpy(s.buf, str, l);
 | |
| 	string_view_consume(&s, l);
 | |
| 
 | |
| 	return start.len - s.len;
 | |
| }
 | |
| 
 | |
| int reftable_encode_key(int *restart, struct string_view dest,
 | |
| 			struct strbuf prev_key, struct strbuf key,
 | |
| 			uint8_t extra)
 | |
| {
 | |
| 	struct string_view start = dest;
 | |
| 	int prefix_len = common_prefix_size(&prev_key, &key);
 | |
| 	uint64_t suffix_len = key.len - prefix_len;
 | |
| 	int n = put_var_int(&dest, (uint64_t)prefix_len);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&dest, n);
 | |
| 
 | |
| 	*restart = (prefix_len == 0);
 | |
| 
 | |
| 	n = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&dest, n);
 | |
| 
 | |
| 	if (dest.len < suffix_len)
 | |
| 		return -1;
 | |
| 	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
 | |
| 	string_view_consume(&dest, suffix_len);
 | |
| 
 | |
| 	return start.len - dest.len;
 | |
| }
 | |
| 
 | |
| int reftable_decode_key(struct strbuf *key, uint8_t *extra,
 | |
| 			struct strbuf last_key, struct string_view in)
 | |
| {
 | |
| 	int start_len = in.len;
 | |
| 	uint64_t prefix_len = 0;
 | |
| 	uint64_t suffix_len = 0;
 | |
| 	int n = get_var_int(&prefix_len, &in);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	if (prefix_len > last_key.len)
 | |
| 		return -1;
 | |
| 
 | |
| 	n = get_var_int(&suffix_len, &in);
 | |
| 	if (n <= 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	*extra = (uint8_t)(suffix_len & 0x7);
 | |
| 	suffix_len >>= 3;
 | |
| 
 | |
| 	if (in.len < suffix_len)
 | |
| 		return -1;
 | |
| 
 | |
| 	strbuf_reset(key);
 | |
| 	strbuf_add(key, last_key.buf, prefix_len);
 | |
| 	strbuf_add(key, in.buf, suffix_len);
 | |
| 	string_view_consume(&in, suffix_len);
 | |
| 
 | |
| 	return start_len - in.len;
 | |
| }
 | |
| 
 | |
| static void reftable_ref_record_key(const void *r, struct strbuf *dest)
 | |
| {
 | |
| 	const struct reftable_ref_record *rec =
 | |
| 		(const struct reftable_ref_record *)r;
 | |
| 	strbuf_reset(dest);
 | |
| 	strbuf_addstr(dest, rec->refname);
 | |
| }
 | |
| 
 | |
| static void reftable_ref_record_copy_from(void *rec, const void *src_rec,
 | |
| 					  int hash_size)
 | |
| {
 | |
| 	struct reftable_ref_record *ref = rec;
 | |
| 	const struct reftable_ref_record *src = src_rec;
 | |
| 	assert(hash_size > 0);
 | |
| 
 | |
| 	/* This is simple and correct, but we could probably reuse the hash
 | |
| 	 * fields. */
 | |
| 	reftable_ref_record_release(ref);
 | |
| 	if (src->refname) {
 | |
| 		ref->refname = xstrdup(src->refname);
 | |
| 	}
 | |
| 	ref->update_index = src->update_index;
 | |
| 	ref->value_type = src->value_type;
 | |
| 	switch (src->value_type) {
 | |
| 	case REFTABLE_REF_DELETION:
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		ref->value.val1 = reftable_malloc(hash_size);
 | |
| 		memcpy(ref->value.val1, src->value.val1, hash_size);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		ref->value.val2.value = reftable_malloc(hash_size);
 | |
| 		memcpy(ref->value.val2.value, src->value.val2.value, hash_size);
 | |
| 		ref->value.val2.target_value = reftable_malloc(hash_size);
 | |
| 		memcpy(ref->value.val2.target_value,
 | |
| 		       src->value.val2.target_value, hash_size);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_SYMREF:
 | |
| 		ref->value.symref = xstrdup(src->value.symref);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static char hexdigit(int c)
 | |
| {
 | |
| 	if (c <= 9)
 | |
| 		return '0' + c;
 | |
| 	return 'a' + (c - 10);
 | |
| }
 | |
| 
 | |
| static void hex_format(char *dest, uint8_t *src, int hash_size)
 | |
| {
 | |
| 	assert(hash_size > 0);
 | |
| 	if (src) {
 | |
| 		int i = 0;
 | |
| 		for (i = 0; i < hash_size; i++) {
 | |
| 			dest[2 * i] = hexdigit(src[i] >> 4);
 | |
| 			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
 | |
| 		}
 | |
| 		dest[2 * hash_size] = 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref,
 | |
| 					 int hash_size)
 | |
| {
 | |
| 	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 | |
| 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
 | |
| 	switch (ref->value_type) {
 | |
| 	case REFTABLE_REF_SYMREF:
 | |
| 		printf("=> %s", ref->value.symref);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		hex_format(hex, ref->value.val2.value, hash_size);
 | |
| 		printf("val 2 %s", hex);
 | |
| 		hex_format(hex, ref->value.val2.target_value,
 | |
| 			   hash_size);
 | |
| 		printf("(T %s)", hex);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		hex_format(hex, ref->value.val1, hash_size);
 | |
| 		printf("val 1 %s", hex);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_DELETION:
 | |
| 		printf("delete");
 | |
| 		break;
 | |
| 	}
 | |
| 	printf("}\n");
 | |
| }
 | |
| 
 | |
| void reftable_ref_record_print(const struct reftable_ref_record *ref,
 | |
| 			       uint32_t hash_id) {
 | |
| 	reftable_ref_record_print_sz(ref, hash_size(hash_id));
 | |
| }
 | |
| 
 | |
| static void reftable_ref_record_release_void(void *rec)
 | |
| {
 | |
| 	reftable_ref_record_release(rec);
 | |
| }
 | |
| 
 | |
| void reftable_ref_record_release(struct reftable_ref_record *ref)
 | |
| {
 | |
| 	switch (ref->value_type) {
 | |
| 	case REFTABLE_REF_SYMREF:
 | |
| 		reftable_free(ref->value.symref);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		reftable_free(ref->value.val2.target_value);
 | |
| 		reftable_free(ref->value.val2.value);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		reftable_free(ref->value.val1);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_DELETION:
 | |
| 		break;
 | |
| 	default:
 | |
| 		abort();
 | |
| 	}
 | |
| 
 | |
| 	reftable_free(ref->refname);
 | |
| 	memset(ref, 0, sizeof(struct reftable_ref_record));
 | |
| }
 | |
| 
 | |
| static uint8_t reftable_ref_record_val_type(const void *rec)
 | |
| {
 | |
| 	const struct reftable_ref_record *r =
 | |
| 		(const struct reftable_ref_record *)rec;
 | |
| 	return r->value_type;
 | |
| }
 | |
| 
 | |
| static int reftable_ref_record_encode(const void *rec, struct string_view s,
 | |
| 				      int hash_size)
 | |
| {
 | |
| 	const struct reftable_ref_record *r =
 | |
| 		(const struct reftable_ref_record *)rec;
 | |
| 	struct string_view start = s;
 | |
| 	int n = put_var_int(&s, r->update_index);
 | |
| 	assert(hash_size > 0);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 
 | |
| 	switch (r->value_type) {
 | |
| 	case REFTABLE_REF_SYMREF:
 | |
| 		n = encode_string(r->value.symref, s);
 | |
| 		if (n < 0) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		string_view_consume(&s, n);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		if (s.len < 2 * hash_size) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		memcpy(s.buf, r->value.val2.value, hash_size);
 | |
| 		string_view_consume(&s, hash_size);
 | |
| 		memcpy(s.buf, r->value.val2.target_value, hash_size);
 | |
| 		string_view_consume(&s, hash_size);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		if (s.len < hash_size) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		memcpy(s.buf, r->value.val1, hash_size);
 | |
| 		string_view_consume(&s, hash_size);
 | |
| 		break;
 | |
| 	case REFTABLE_REF_DELETION:
 | |
| 		break;
 | |
| 	default:
 | |
| 		abort();
 | |
| 	}
 | |
| 
 | |
| 	return start.len - s.len;
 | |
| }
 | |
| 
 | |
| static int reftable_ref_record_decode(void *rec, struct strbuf key,
 | |
| 				      uint8_t val_type, struct string_view in,
 | |
| 				      int hash_size)
 | |
| {
 | |
| 	struct reftable_ref_record *r = rec;
 | |
| 	struct string_view start = in;
 | |
| 	uint64_t update_index = 0;
 | |
| 	int n = get_var_int(&update_index, &in);
 | |
| 	if (n < 0)
 | |
| 		return n;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	reftable_ref_record_release(r);
 | |
| 
 | |
| 	assert(hash_size > 0);
 | |
| 
 | |
| 	r->refname = reftable_realloc(r->refname, key.len + 1);
 | |
| 	memcpy(r->refname, key.buf, key.len);
 | |
| 	r->update_index = update_index;
 | |
| 	r->refname[key.len] = 0;
 | |
| 	r->value_type = val_type;
 | |
| 	switch (val_type) {
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		if (in.len < hash_size) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		r->value.val1 = reftable_malloc(hash_size);
 | |
| 		memcpy(r->value.val1, in.buf, hash_size);
 | |
| 		string_view_consume(&in, hash_size);
 | |
| 		break;
 | |
| 
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		if (in.len < 2 * hash_size) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		r->value.val2.value = reftable_malloc(hash_size);
 | |
| 		memcpy(r->value.val2.value, in.buf, hash_size);
 | |
| 		string_view_consume(&in, hash_size);
 | |
| 
 | |
| 		r->value.val2.target_value = reftable_malloc(hash_size);
 | |
| 		memcpy(r->value.val2.target_value, in.buf, hash_size);
 | |
| 		string_view_consume(&in, hash_size);
 | |
| 		break;
 | |
| 
 | |
| 	case REFTABLE_REF_SYMREF: {
 | |
| 		struct strbuf dest = STRBUF_INIT;
 | |
| 		int n = decode_string(&dest, in);
 | |
| 		if (n < 0) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		string_view_consume(&in, n);
 | |
| 		r->value.symref = dest.buf;
 | |
| 	} break;
 | |
| 
 | |
| 	case REFTABLE_REF_DELETION:
 | |
| 		break;
 | |
| 	default:
 | |
| 		abort();
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return start.len - in.len;
 | |
| }
 | |
| 
 | |
| static int reftable_ref_record_is_deletion_void(const void *p)
 | |
| {
 | |
| 	return reftable_ref_record_is_deletion(
 | |
| 		(const struct reftable_ref_record *)p);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int reftable_ref_record_equal_void(const void *a,
 | |
| 					  const void *b, int hash_size)
 | |
| {
 | |
| 	struct reftable_ref_record *ra = (struct reftable_ref_record *) a;
 | |
| 	struct reftable_ref_record *rb = (struct reftable_ref_record *) b;
 | |
| 	return reftable_ref_record_equal(ra, rb, hash_size);
 | |
| }
 | |
| 
 | |
| static void reftable_ref_record_print_void(const void *rec,
 | |
| 					   int hash_size)
 | |
| {
 | |
| 	reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size);
 | |
| }
 | |
| 
 | |
| static struct reftable_record_vtable reftable_ref_record_vtable = {
 | |
| 	.key = &reftable_ref_record_key,
 | |
| 	.type = BLOCK_TYPE_REF,
 | |
| 	.copy_from = &reftable_ref_record_copy_from,
 | |
| 	.val_type = &reftable_ref_record_val_type,
 | |
| 	.encode = &reftable_ref_record_encode,
 | |
| 	.decode = &reftable_ref_record_decode,
 | |
| 	.release = &reftable_ref_record_release_void,
 | |
| 	.is_deletion = &reftable_ref_record_is_deletion_void,
 | |
| 	.equal = &reftable_ref_record_equal_void,
 | |
| 	.print = &reftable_ref_record_print_void,
 | |
| };
 | |
| 
 | |
| static void reftable_obj_record_key(const void *r, struct strbuf *dest)
 | |
| {
 | |
| 	const struct reftable_obj_record *rec =
 | |
| 		(const struct reftable_obj_record *)r;
 | |
| 	strbuf_reset(dest);
 | |
| 	strbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len);
 | |
| }
 | |
| 
 | |
| static void reftable_obj_record_release(void *rec)
 | |
| {
 | |
| 	struct reftable_obj_record *obj = rec;
 | |
| 	FREE_AND_NULL(obj->hash_prefix);
 | |
| 	FREE_AND_NULL(obj->offsets);
 | |
| 	memset(obj, 0, sizeof(struct reftable_obj_record));
 | |
| }
 | |
| 
 | |
| static void reftable_obj_record_print(const void *rec, int hash_size)
 | |
| {
 | |
| 	const struct reftable_obj_record *obj = rec;
 | |
| 	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 | |
| 	struct strbuf offset_str = STRBUF_INIT;
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < obj->offset_len; i++)
 | |
| 		strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]);
 | |
| 	hex_format(hex, obj->hash_prefix, obj->hash_prefix_len);
 | |
| 	printf("prefix %s (len %d), offsets [%s]\n",
 | |
| 	       hex, obj->hash_prefix_len, offset_str.buf);
 | |
| 	strbuf_release(&offset_str);
 | |
| }
 | |
| 
 | |
| static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
 | |
| 					  int hash_size)
 | |
| {
 | |
| 	struct reftable_obj_record *obj = rec;
 | |
| 	const struct reftable_obj_record *src =
 | |
| 		(const struct reftable_obj_record *)src_rec;
 | |
| 
 | |
| 	reftable_obj_record_release(obj);
 | |
| 	obj->hash_prefix = reftable_malloc(src->hash_prefix_len);
 | |
| 	obj->hash_prefix_len = src->hash_prefix_len;
 | |
| 	if (src->hash_prefix_len)
 | |
| 		memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
 | |
| 
 | |
| 	obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t));
 | |
| 	obj->offset_len = src->offset_len;
 | |
| 	COPY_ARRAY(obj->offsets, src->offsets, src->offset_len);
 | |
| }
 | |
| 
 | |
| static uint8_t reftable_obj_record_val_type(const void *rec)
 | |
| {
 | |
| 	const struct reftable_obj_record *r = rec;
 | |
| 	if (r->offset_len > 0 && r->offset_len < 8)
 | |
| 		return r->offset_len;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int reftable_obj_record_encode(const void *rec, struct string_view s,
 | |
| 				      int hash_size)
 | |
| {
 | |
| 	const struct reftable_obj_record *r = rec;
 | |
| 	struct string_view start = s;
 | |
| 	int i = 0;
 | |
| 	int n = 0;
 | |
| 	uint64_t last = 0;
 | |
| 	if (r->offset_len == 0 || r->offset_len >= 8) {
 | |
| 		n = put_var_int(&s, r->offset_len);
 | |
| 		if (n < 0) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		string_view_consume(&s, n);
 | |
| 	}
 | |
| 	if (r->offset_len == 0)
 | |
| 		return start.len - s.len;
 | |
| 	n = put_var_int(&s, r->offsets[0]);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 
 | |
| 	last = r->offsets[0];
 | |
| 	for (i = 1; i < r->offset_len; i++) {
 | |
| 		int n = put_var_int(&s, r->offsets[i] - last);
 | |
| 		if (n < 0) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 		string_view_consume(&s, n);
 | |
| 		last = r->offsets[i];
 | |
| 	}
 | |
| 	return start.len - s.len;
 | |
| }
 | |
| 
 | |
| static int reftable_obj_record_decode(void *rec, struct strbuf key,
 | |
| 				      uint8_t val_type, struct string_view in,
 | |
| 				      int hash_size)
 | |
| {
 | |
| 	struct string_view start = in;
 | |
| 	struct reftable_obj_record *r = rec;
 | |
| 	uint64_t count = val_type;
 | |
| 	int n = 0;
 | |
| 	uint64_t last;
 | |
| 	int j;
 | |
| 	r->hash_prefix = reftable_malloc(key.len);
 | |
| 	memcpy(r->hash_prefix, key.buf, key.len);
 | |
| 	r->hash_prefix_len = key.len;
 | |
| 
 | |
| 	if (val_type == 0) {
 | |
| 		n = get_var_int(&count, &in);
 | |
| 		if (n < 0) {
 | |
| 			return n;
 | |
| 		}
 | |
| 
 | |
| 		string_view_consume(&in, n);
 | |
| 	}
 | |
| 
 | |
| 	r->offsets = NULL;
 | |
| 	r->offset_len = 0;
 | |
| 	if (count == 0)
 | |
| 		return start.len - in.len;
 | |
| 
 | |
| 	r->offsets = reftable_malloc(count * sizeof(uint64_t));
 | |
| 	r->offset_len = count;
 | |
| 
 | |
| 	n = get_var_int(&r->offsets[0], &in);
 | |
| 	if (n < 0)
 | |
| 		return n;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	last = r->offsets[0];
 | |
| 	j = 1;
 | |
| 	while (j < count) {
 | |
| 		uint64_t delta = 0;
 | |
| 		int n = get_var_int(&delta, &in);
 | |
| 		if (n < 0) {
 | |
| 			return n;
 | |
| 		}
 | |
| 		string_view_consume(&in, n);
 | |
| 
 | |
| 		last = r->offsets[j] = (delta + last);
 | |
| 		j++;
 | |
| 	}
 | |
| 	return start.len - in.len;
 | |
| }
 | |
| 
 | |
| static int not_a_deletion(const void *p)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size)
 | |
| {
 | |
| 	struct reftable_obj_record *ra = (struct reftable_obj_record *) a;
 | |
| 	struct reftable_obj_record *rb = (struct reftable_obj_record *) b;
 | |
| 
 | |
| 	if (ra->hash_prefix_len != rb->hash_prefix_len
 | |
| 	    || ra->offset_len != rb->offset_len)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (ra->hash_prefix_len &&
 | |
| 	    memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len))
 | |
| 		return 0;
 | |
| 	if (ra->offset_len &&
 | |
| 	    memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t)))
 | |
| 		return 0;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static struct reftable_record_vtable reftable_obj_record_vtable = {
 | |
| 	.key = &reftable_obj_record_key,
 | |
| 	.type = BLOCK_TYPE_OBJ,
 | |
| 	.copy_from = &reftable_obj_record_copy_from,
 | |
| 	.val_type = &reftable_obj_record_val_type,
 | |
| 	.encode = &reftable_obj_record_encode,
 | |
| 	.decode = &reftable_obj_record_decode,
 | |
| 	.release = &reftable_obj_record_release,
 | |
| 	.is_deletion = ¬_a_deletion,
 | |
| 	.equal = &reftable_obj_record_equal_void,
 | |
| 	.print = &reftable_obj_record_print,
 | |
| };
 | |
| 
 | |
| static void reftable_log_record_print_sz(struct reftable_log_record *log,
 | |
| 					 int hash_size)
 | |
| {
 | |
| 	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 | |
| 
 | |
| 	switch (log->value_type) {
 | |
| 	case REFTABLE_LOG_DELETION:
 | |
| 		printf("log{%s(%" PRIu64 ") delete\n", log->refname,
 | |
| 		       log->update_index);
 | |
| 		break;
 | |
| 	case REFTABLE_LOG_UPDATE:
 | |
| 		printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
 | |
| 		       log->refname, log->update_index,
 | |
| 		       log->value.update.name ? log->value.update.name : "",
 | |
| 		       log->value.update.email ? log->value.update.email : "",
 | |
| 		       log->value.update.time,
 | |
| 		       log->value.update.tz_offset);
 | |
| 		hex_format(hex, log->value.update.old_hash, hash_size);
 | |
| 		printf("%s => ", hex);
 | |
| 		hex_format(hex, log->value.update.new_hash, hash_size);
 | |
| 		printf("%s\n\n%s\n}\n", hex,
 | |
| 		       log->value.update.message ? log->value.update.message : "");
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void reftable_log_record_print(struct reftable_log_record *log,
 | |
| 				      uint32_t hash_id)
 | |
| {
 | |
| 	reftable_log_record_print_sz(log, hash_size(hash_id));
 | |
| }
 | |
| 
 | |
| static void reftable_log_record_key(const void *r, struct strbuf *dest)
 | |
| {
 | |
| 	const struct reftable_log_record *rec =
 | |
| 		(const struct reftable_log_record *)r;
 | |
| 	int len = strlen(rec->refname);
 | |
| 	uint8_t i64[8];
 | |
| 	uint64_t ts = 0;
 | |
| 	strbuf_reset(dest);
 | |
| 	strbuf_add(dest, (uint8_t *)rec->refname, len + 1);
 | |
| 
 | |
| 	ts = (~ts) - rec->update_index;
 | |
| 	put_be64(&i64[0], ts);
 | |
| 	strbuf_add(dest, i64, sizeof(i64));
 | |
| }
 | |
| 
 | |
| static void reftable_log_record_copy_from(void *rec, const void *src_rec,
 | |
| 					  int hash_size)
 | |
| {
 | |
| 	struct reftable_log_record *dst = rec;
 | |
| 	const struct reftable_log_record *src =
 | |
| 		(const struct reftable_log_record *)src_rec;
 | |
| 
 | |
| 	reftable_log_record_release(dst);
 | |
| 	*dst = *src;
 | |
| 	if (dst->refname) {
 | |
| 		dst->refname = xstrdup(dst->refname);
 | |
| 	}
 | |
| 	switch (dst->value_type) {
 | |
| 	case REFTABLE_LOG_DELETION:
 | |
| 		break;
 | |
| 	case REFTABLE_LOG_UPDATE:
 | |
| 		if (dst->value.update.email) {
 | |
| 			dst->value.update.email =
 | |
| 				xstrdup(dst->value.update.email);
 | |
| 		}
 | |
| 		if (dst->value.update.name) {
 | |
| 			dst->value.update.name =
 | |
| 				xstrdup(dst->value.update.name);
 | |
| 		}
 | |
| 		if (dst->value.update.message) {
 | |
| 			dst->value.update.message =
 | |
| 				xstrdup(dst->value.update.message);
 | |
| 		}
 | |
| 
 | |
| 		if (dst->value.update.new_hash) {
 | |
| 			dst->value.update.new_hash = reftable_malloc(hash_size);
 | |
| 			memcpy(dst->value.update.new_hash,
 | |
| 			       src->value.update.new_hash, hash_size);
 | |
| 		}
 | |
| 		if (dst->value.update.old_hash) {
 | |
| 			dst->value.update.old_hash = reftable_malloc(hash_size);
 | |
| 			memcpy(dst->value.update.old_hash,
 | |
| 			       src->value.update.old_hash, hash_size);
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void reftable_log_record_release_void(void *rec)
 | |
| {
 | |
| 	struct reftable_log_record *r = rec;
 | |
| 	reftable_log_record_release(r);
 | |
| }
 | |
| 
 | |
| void reftable_log_record_release(struct reftable_log_record *r)
 | |
| {
 | |
| 	reftable_free(r->refname);
 | |
| 	switch (r->value_type) {
 | |
| 	case REFTABLE_LOG_DELETION:
 | |
| 		break;
 | |
| 	case REFTABLE_LOG_UPDATE:
 | |
| 		reftable_free(r->value.update.new_hash);
 | |
| 		reftable_free(r->value.update.old_hash);
 | |
| 		reftable_free(r->value.update.name);
 | |
| 		reftable_free(r->value.update.email);
 | |
| 		reftable_free(r->value.update.message);
 | |
| 		break;
 | |
| 	}
 | |
| 	memset(r, 0, sizeof(struct reftable_log_record));
 | |
| }
 | |
| 
 | |
| static uint8_t reftable_log_record_val_type(const void *rec)
 | |
| {
 | |
| 	const struct reftable_log_record *log =
 | |
| 		(const struct reftable_log_record *)rec;
 | |
| 
 | |
| 	return reftable_log_record_is_deletion(log) ? 0 : 1;
 | |
| }
 | |
| 
 | |
| static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 };
 | |
| 
 | |
| static int reftable_log_record_encode(const void *rec, struct string_view s,
 | |
| 				      int hash_size)
 | |
| {
 | |
| 	const struct reftable_log_record *r = rec;
 | |
| 	struct string_view start = s;
 | |
| 	int n = 0;
 | |
| 	uint8_t *oldh = NULL;
 | |
| 	uint8_t *newh = NULL;
 | |
| 	if (reftable_log_record_is_deletion(r))
 | |
| 		return 0;
 | |
| 
 | |
| 	oldh = r->value.update.old_hash;
 | |
| 	newh = r->value.update.new_hash;
 | |
| 	if (!oldh) {
 | |
| 		oldh = zero;
 | |
| 	}
 | |
| 	if (!newh) {
 | |
| 		newh = zero;
 | |
| 	}
 | |
| 
 | |
| 	if (s.len < 2 * hash_size)
 | |
| 		return -1;
 | |
| 
 | |
| 	memcpy(s.buf, oldh, hash_size);
 | |
| 	memcpy(s.buf + hash_size, newh, hash_size);
 | |
| 	string_view_consume(&s, 2 * hash_size);
 | |
| 
 | |
| 	n = encode_string(r->value.update.name ? r->value.update.name : "", s);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 
 | |
| 	n = encode_string(r->value.update.email ? r->value.update.email : "",
 | |
| 			  s);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 
 | |
| 	n = put_var_int(&s, r->value.update.time);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 
 | |
| 	if (s.len < 2)
 | |
| 		return -1;
 | |
| 
 | |
| 	put_be16(s.buf, r->value.update.tz_offset);
 | |
| 	string_view_consume(&s, 2);
 | |
| 
 | |
| 	n = encode_string(
 | |
| 		r->value.update.message ? r->value.update.message : "", s);
 | |
| 	if (n < 0)
 | |
| 		return -1;
 | |
| 	string_view_consume(&s, n);
 | |
| 
 | |
| 	return start.len - s.len;
 | |
| }
 | |
| 
 | |
| static int reftable_log_record_decode(void *rec, struct strbuf key,
 | |
| 				      uint8_t val_type, struct string_view in,
 | |
| 				      int hash_size)
 | |
| {
 | |
| 	struct string_view start = in;
 | |
| 	struct reftable_log_record *r = rec;
 | |
| 	uint64_t max = 0;
 | |
| 	uint64_t ts = 0;
 | |
| 	struct strbuf dest = STRBUF_INIT;
 | |
| 	int n;
 | |
| 
 | |
| 	if (key.len <= 9 || key.buf[key.len - 9] != 0)
 | |
| 		return REFTABLE_FORMAT_ERROR;
 | |
| 
 | |
| 	r->refname = reftable_realloc(r->refname, key.len - 8);
 | |
| 	memcpy(r->refname, key.buf, key.len - 8);
 | |
| 	ts = get_be64(key.buf + key.len - 8);
 | |
| 
 | |
| 	r->update_index = (~max) - ts;
 | |
| 
 | |
| 	if (val_type != r->value_type) {
 | |
| 		switch (r->value_type) {
 | |
| 		case REFTABLE_LOG_UPDATE:
 | |
| 			FREE_AND_NULL(r->value.update.old_hash);
 | |
| 			FREE_AND_NULL(r->value.update.new_hash);
 | |
| 			FREE_AND_NULL(r->value.update.message);
 | |
| 			FREE_AND_NULL(r->value.update.email);
 | |
| 			FREE_AND_NULL(r->value.update.name);
 | |
| 			break;
 | |
| 		case REFTABLE_LOG_DELETION:
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r->value_type = val_type;
 | |
| 	if (val_type == REFTABLE_LOG_DELETION)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (in.len < 2 * hash_size)
 | |
| 		return REFTABLE_FORMAT_ERROR;
 | |
| 
 | |
| 	r->value.update.old_hash =
 | |
| 		reftable_realloc(r->value.update.old_hash, hash_size);
 | |
| 	r->value.update.new_hash =
 | |
| 		reftable_realloc(r->value.update.new_hash, hash_size);
 | |
| 
 | |
| 	memcpy(r->value.update.old_hash, in.buf, hash_size);
 | |
| 	memcpy(r->value.update.new_hash, in.buf + hash_size, hash_size);
 | |
| 
 | |
| 	string_view_consume(&in, 2 * hash_size);
 | |
| 
 | |
| 	n = decode_string(&dest, in);
 | |
| 	if (n < 0)
 | |
| 		goto done;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	r->value.update.name =
 | |
| 		reftable_realloc(r->value.update.name, dest.len + 1);
 | |
| 	memcpy(r->value.update.name, dest.buf, dest.len);
 | |
| 	r->value.update.name[dest.len] = 0;
 | |
| 
 | |
| 	strbuf_reset(&dest);
 | |
| 	n = decode_string(&dest, in);
 | |
| 	if (n < 0)
 | |
| 		goto done;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	r->value.update.email =
 | |
| 		reftable_realloc(r->value.update.email, dest.len + 1);
 | |
| 	memcpy(r->value.update.email, dest.buf, dest.len);
 | |
| 	r->value.update.email[dest.len] = 0;
 | |
| 
 | |
| 	ts = 0;
 | |
| 	n = get_var_int(&ts, &in);
 | |
| 	if (n < 0)
 | |
| 		goto done;
 | |
| 	string_view_consume(&in, n);
 | |
| 	r->value.update.time = ts;
 | |
| 	if (in.len < 2)
 | |
| 		goto done;
 | |
| 
 | |
| 	r->value.update.tz_offset = get_be16(in.buf);
 | |
| 	string_view_consume(&in, 2);
 | |
| 
 | |
| 	strbuf_reset(&dest);
 | |
| 	n = decode_string(&dest, in);
 | |
| 	if (n < 0)
 | |
| 		goto done;
 | |
| 	string_view_consume(&in, n);
 | |
| 
 | |
| 	r->value.update.message =
 | |
| 		reftable_realloc(r->value.update.message, dest.len + 1);
 | |
| 	memcpy(r->value.update.message, dest.buf, dest.len);
 | |
| 	r->value.update.message[dest.len] = 0;
 | |
| 
 | |
| 	strbuf_release(&dest);
 | |
| 	return start.len - in.len;
 | |
| 
 | |
| done:
 | |
| 	strbuf_release(&dest);
 | |
| 	return REFTABLE_FORMAT_ERROR;
 | |
| }
 | |
| 
 | |
| static int null_streq(char *a, char *b)
 | |
| {
 | |
| 	char *empty = "";
 | |
| 	if (!a)
 | |
| 		a = empty;
 | |
| 
 | |
| 	if (!b)
 | |
| 		b = empty;
 | |
| 
 | |
| 	return 0 == strcmp(a, b);
 | |
| }
 | |
| 
 | |
| static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)
 | |
| {
 | |
| 	if (!a)
 | |
| 		a = zero;
 | |
| 
 | |
| 	if (!b)
 | |
| 		b = zero;
 | |
| 
 | |
| 	return !memcmp(a, b, sz);
 | |
| }
 | |
| 
 | |
| static int reftable_log_record_equal_void(const void *a,
 | |
| 					  const void *b, int hash_size)
 | |
| {
 | |
| 	return reftable_log_record_equal((struct reftable_log_record *) a,
 | |
| 					 (struct reftable_log_record *) b,
 | |
| 					 hash_size);
 | |
| }
 | |
| 
 | |
| int reftable_log_record_equal(const struct reftable_log_record *a,
 | |
| 			      const struct reftable_log_record *b, int hash_size)
 | |
| {
 | |
| 	if (!(null_streq(a->refname, b->refname) &&
 | |
| 	      a->update_index == b->update_index &&
 | |
| 	      a->value_type == b->value_type))
 | |
| 		return 0;
 | |
| 
 | |
| 	switch (a->value_type) {
 | |
| 	case REFTABLE_LOG_DELETION:
 | |
| 		return 1;
 | |
| 	case REFTABLE_LOG_UPDATE:
 | |
| 		return null_streq(a->value.update.name, b->value.update.name) &&
 | |
| 		       a->value.update.time == b->value.update.time &&
 | |
| 		       a->value.update.tz_offset == b->value.update.tz_offset &&
 | |
| 		       null_streq(a->value.update.email,
 | |
| 				  b->value.update.email) &&
 | |
| 		       null_streq(a->value.update.message,
 | |
| 				  b->value.update.message) &&
 | |
| 		       zero_hash_eq(a->value.update.old_hash,
 | |
| 				    b->value.update.old_hash, hash_size) &&
 | |
| 		       zero_hash_eq(a->value.update.new_hash,
 | |
| 				    b->value.update.new_hash, hash_size);
 | |
| 	}
 | |
| 
 | |
| 	abort();
 | |
| }
 | |
| 
 | |
| static int reftable_log_record_is_deletion_void(const void *p)
 | |
| {
 | |
| 	return reftable_log_record_is_deletion(
 | |
| 		(const struct reftable_log_record *)p);
 | |
| }
 | |
| 
 | |
| static void reftable_log_record_print_void(const void *rec, int hash_size)
 | |
| {
 | |
| 	reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size);
 | |
| }
 | |
| 
 | |
| static struct reftable_record_vtable reftable_log_record_vtable = {
 | |
| 	.key = &reftable_log_record_key,
 | |
| 	.type = BLOCK_TYPE_LOG,
 | |
| 	.copy_from = &reftable_log_record_copy_from,
 | |
| 	.val_type = &reftable_log_record_val_type,
 | |
| 	.encode = &reftable_log_record_encode,
 | |
| 	.decode = &reftable_log_record_decode,
 | |
| 	.release = &reftable_log_record_release_void,
 | |
| 	.is_deletion = &reftable_log_record_is_deletion_void,
 | |
| 	.equal = &reftable_log_record_equal_void,
 | |
| 	.print = &reftable_log_record_print_void,
 | |
| };
 | |
| 
 | |
| static void reftable_index_record_key(const void *r, struct strbuf *dest)
 | |
| {
 | |
| 	const struct reftable_index_record *rec = r;
 | |
| 	strbuf_reset(dest);
 | |
| 	strbuf_addbuf(dest, &rec->last_key);
 | |
| }
 | |
| 
 | |
| static void reftable_index_record_copy_from(void *rec, const void *src_rec,
 | |
| 					    int hash_size)
 | |
| {
 | |
| 	struct reftable_index_record *dst = rec;
 | |
| 	const struct reftable_index_record *src = src_rec;
 | |
| 
 | |
| 	strbuf_reset(&dst->last_key);
 | |
| 	strbuf_addbuf(&dst->last_key, &src->last_key);
 | |
| 	dst->offset = src->offset;
 | |
| }
 | |
| 
 | |
| static void reftable_index_record_release(void *rec)
 | |
| {
 | |
| 	struct reftable_index_record *idx = rec;
 | |
| 	strbuf_release(&idx->last_key);
 | |
| }
 | |
| 
 | |
| static uint8_t reftable_index_record_val_type(const void *rec)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int reftable_index_record_encode(const void *rec, struct string_view out,
 | |
| 					int hash_size)
 | |
| {
 | |
| 	const struct reftable_index_record *r =
 | |
| 		(const struct reftable_index_record *)rec;
 | |
| 	struct string_view start = out;
 | |
| 
 | |
| 	int n = put_var_int(&out, r->offset);
 | |
| 	if (n < 0)
 | |
| 		return n;
 | |
| 
 | |
| 	string_view_consume(&out, n);
 | |
| 
 | |
| 	return start.len - out.len;
 | |
| }
 | |
| 
 | |
| static int reftable_index_record_decode(void *rec, struct strbuf key,
 | |
| 					uint8_t val_type, struct string_view in,
 | |
| 					int hash_size)
 | |
| {
 | |
| 	struct string_view start = in;
 | |
| 	struct reftable_index_record *r = rec;
 | |
| 	int n = 0;
 | |
| 
 | |
| 	strbuf_reset(&r->last_key);
 | |
| 	strbuf_addbuf(&r->last_key, &key);
 | |
| 
 | |
| 	n = get_var_int(&r->offset, &in);
 | |
| 	if (n < 0)
 | |
| 		return n;
 | |
| 
 | |
| 	string_view_consume(&in, n);
 | |
| 	return start.len - in.len;
 | |
| }
 | |
| 
 | |
| static int reftable_index_record_equal(const void *a, const void *b, int hash_size)
 | |
| {
 | |
| 	struct reftable_index_record *ia = (struct reftable_index_record *) a;
 | |
| 	struct reftable_index_record *ib = (struct reftable_index_record *) b;
 | |
| 
 | |
| 	return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key);
 | |
| }
 | |
| 
 | |
| static void reftable_index_record_print(const void *rec, int hash_size)
 | |
| {
 | |
| 	const struct reftable_index_record *idx = rec;
 | |
| 	/* TODO: escape null chars? */
 | |
| 	printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset);
 | |
| }
 | |
| 
 | |
| static struct reftable_record_vtable reftable_index_record_vtable = {
 | |
| 	.key = &reftable_index_record_key,
 | |
| 	.type = BLOCK_TYPE_INDEX,
 | |
| 	.copy_from = &reftable_index_record_copy_from,
 | |
| 	.val_type = &reftable_index_record_val_type,
 | |
| 	.encode = &reftable_index_record_encode,
 | |
| 	.decode = &reftable_index_record_decode,
 | |
| 	.release = &reftable_index_record_release,
 | |
| 	.is_deletion = ¬_a_deletion,
 | |
| 	.equal = &reftable_index_record_equal,
 | |
| 	.print = &reftable_index_record_print,
 | |
| };
 | |
| 
 | |
| void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
 | |
| {
 | |
| 	reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 | |
| }
 | |
| 
 | |
| uint8_t reftable_record_type(struct reftable_record *rec)
 | |
| {
 | |
| 	return rec->type;
 | |
| }
 | |
| 
 | |
| int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
 | |
| 			   int hash_size)
 | |
| {
 | |
| 	return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
 | |
| 						   dest, hash_size);
 | |
| }
 | |
| 
 | |
| void reftable_record_copy_from(struct reftable_record *rec,
 | |
| 			       struct reftable_record *src, int hash_size)
 | |
| {
 | |
| 	assert(src->type == rec->type);
 | |
| 
 | |
| 	reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
 | |
| 					       reftable_record_data(src),
 | |
| 					       hash_size);
 | |
| }
 | |
| 
 | |
| uint8_t reftable_record_val_type(struct reftable_record *rec)
 | |
| {
 | |
| 	return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
 | |
| }
 | |
| 
 | |
| int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 | |
| 			   uint8_t extra, struct string_view src, int hash_size)
 | |
| {
 | |
| 	return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
 | |
| 						   key, extra, src, hash_size);
 | |
| }
 | |
| 
 | |
| void reftable_record_release(struct reftable_record *rec)
 | |
| {
 | |
| 	reftable_record_vtable(rec)->release(reftable_record_data(rec));
 | |
| }
 | |
| 
 | |
| int reftable_record_is_deletion(struct reftable_record *rec)
 | |
| {
 | |
| 	return reftable_record_vtable(rec)->is_deletion(
 | |
| 		reftable_record_data(rec));
 | |
| }
 | |
| 
 | |
| int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size)
 | |
| {
 | |
| 	if (a->type != b->type)
 | |
| 		return 0;
 | |
| 	return reftable_record_vtable(a)->equal(
 | |
| 		reftable_record_data(a), reftable_record_data(b), hash_size);
 | |
| }
 | |
| 
 | |
| static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
 | |
| {
 | |
| 	if (a && b)
 | |
| 		return !memcmp(a, b, hash_size);
 | |
| 
 | |
| 	return a == b;
 | |
| }
 | |
| 
 | |
| int reftable_ref_record_equal(const struct reftable_ref_record *a,
 | |
| 			      const struct reftable_ref_record *b, int hash_size)
 | |
| {
 | |
| 	assert(hash_size > 0);
 | |
| 	if (!null_streq(a->refname, b->refname))
 | |
| 		return 0;
 | |
| 
 | |
| 	if (a->update_index != b->update_index ||
 | |
| 	    a->value_type != b->value_type)
 | |
| 		return 0;
 | |
| 
 | |
| 	switch (a->value_type) {
 | |
| 	case REFTABLE_REF_SYMREF:
 | |
| 		return !strcmp(a->value.symref, b->value.symref);
 | |
| 	case REFTABLE_REF_VAL2:
 | |
| 		return hash_equal(a->value.val2.value, b->value.val2.value,
 | |
| 				  hash_size) &&
 | |
| 		       hash_equal(a->value.val2.target_value,
 | |
| 				  b->value.val2.target_value, hash_size);
 | |
| 	case REFTABLE_REF_VAL1:
 | |
| 		return hash_equal(a->value.val1, b->value.val1, hash_size);
 | |
| 	case REFTABLE_REF_DELETION:
 | |
| 		return 1;
 | |
| 	default:
 | |
| 		abort();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int reftable_ref_record_compare_name(const void *a, const void *b)
 | |
| {
 | |
| 	return strcmp(((struct reftable_ref_record *)a)->refname,
 | |
| 		      ((struct reftable_ref_record *)b)->refname);
 | |
| }
 | |
| 
 | |
| int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref)
 | |
| {
 | |
| 	return ref->value_type == REFTABLE_REF_DELETION;
 | |
| }
 | |
| 
 | |
| int reftable_log_record_compare_key(const void *a, const void *b)
 | |
| {
 | |
| 	const struct reftable_log_record *la = a;
 | |
| 	const struct reftable_log_record *lb = b;
 | |
| 
 | |
| 	int cmp = strcmp(la->refname, lb->refname);
 | |
| 	if (cmp)
 | |
| 		return cmp;
 | |
| 	if (la->update_index > lb->update_index)
 | |
| 		return -1;
 | |
| 	return (la->update_index < lb->update_index) ? 1 : 0;
 | |
| }
 | |
| 
 | |
| int reftable_log_record_is_deletion(const struct reftable_log_record *log)
 | |
| {
 | |
| 	return (log->value_type == REFTABLE_LOG_DELETION);
 | |
| }
 | |
| 
 | |
| void string_view_consume(struct string_view *s, int n)
 | |
| {
 | |
| 	s->buf += n;
 | |
| 	s->len -= n;
 | |
| }
 | |
| 
 | |
| static void *reftable_record_data(struct reftable_record *rec)
 | |
| {
 | |
| 	switch (rec->type) {
 | |
| 	case BLOCK_TYPE_REF:
 | |
| 		return &rec->u.ref;
 | |
| 	case BLOCK_TYPE_LOG:
 | |
| 		return &rec->u.log;
 | |
| 	case BLOCK_TYPE_INDEX:
 | |
| 		return &rec->u.idx;
 | |
| 	case BLOCK_TYPE_OBJ:
 | |
| 		return &rec->u.obj;
 | |
| 	}
 | |
| 	abort();
 | |
| }
 | |
| 
 | |
| static struct reftable_record_vtable *
 | |
| reftable_record_vtable(struct reftable_record *rec)
 | |
| {
 | |
| 	switch (rec->type) {
 | |
| 	case BLOCK_TYPE_REF:
 | |
| 		return &reftable_ref_record_vtable;
 | |
| 	case BLOCK_TYPE_LOG:
 | |
| 		return &reftable_log_record_vtable;
 | |
| 	case BLOCK_TYPE_INDEX:
 | |
| 		return &reftable_index_record_vtable;
 | |
| 	case BLOCK_TYPE_OBJ:
 | |
| 		return &reftable_obj_record_vtable;
 | |
| 	}
 | |
| 	abort();
 | |
| }
 | |
| 
 | |
| struct reftable_record reftable_new_record(uint8_t typ)
 | |
| {
 | |
| 	struct reftable_record clean = {
 | |
| 		.type = typ,
 | |
| 	};
 | |
| 
 | |
| 	/* the following is involved, but the naive solution (just return
 | |
| 	 * `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage
 | |
| 	 * clean.u.obj.offsets pointer on Windows VS CI.  Go figure.
 | |
| 	 */
 | |
| 	switch (typ) {
 | |
| 	case BLOCK_TYPE_OBJ:
 | |
| 	{
 | |
| 		struct reftable_obj_record obj = { 0 };
 | |
| 		clean.u.obj = obj;
 | |
| 		break;
 | |
| 	}
 | |
| 	case BLOCK_TYPE_INDEX:
 | |
| 	{
 | |
| 		struct reftable_index_record idx = {
 | |
| 			.last_key = STRBUF_INIT,
 | |
| 		};
 | |
| 		clean.u.idx = idx;
 | |
| 		break;
 | |
| 	}
 | |
| 	case BLOCK_TYPE_REF:
 | |
| 	{
 | |
| 		struct reftable_ref_record ref = { 0 };
 | |
| 		clean.u.ref = ref;
 | |
| 		break;
 | |
| 	}
 | |
| 	case BLOCK_TYPE_LOG:
 | |
| 	{
 | |
| 		struct reftable_log_record log = { 0 };
 | |
| 		clean.u.log = log;
 | |
| 		break;
 | |
| 	}
 | |
| 	}
 | |
| 	return clean;
 | |
| }
 | |
| 
 | |
| void reftable_record_print(struct reftable_record *rec, int hash_size)
 | |
| {
 | |
| 	printf("'%c': ", rec->type);
 | |
| 	reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size);
 | |
| }
 |