reftable: implement stack, a mutable database of reftable files.
Signed-off-by: Han-Wen Nienhuys <hanwen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Han-Wen Nienhuys committed
Oct 7, 2021 at 20:25 UTC
e48d427268f965bf381a2f4fa445b5ade7913880
6 files changed
+2520
Makefile
+1
@@ -2479,6 +2479,7 @@ REFTABLE_TEST_OBJS += reftable/pq_test.o
2479
REFTABLE_TEST_OBJS += reftable/record_test.o
2480
REFTABLE_TEST_OBJS += reftable/readwrite_test.o
2481
REFTABLE_TEST_OBJS += reftable/refname_test.o
2482
+REFTABLE_TEST_OBJS += reftable/stack_test.o
2483
REFTABLE_TEST_OBJS += reftable/test_framework.o
2484
REFTABLE_TEST_OBJS += reftable/tree_test.o
2485
reftable/reftable-stack.h
new
+128
@@ -0,0 +1,128 @@
1
+/*
2
+Copyright 2020 Google LLC
3
+
4
+Use of this source code is governed by a BSD-style
5
+license that can be found in the LICENSE file or at
6
+https://developers.google.com/open-source/licenses/bsd
7
+*/
8
+
9
+#ifndef REFTABLE_STACK_H
10
+#define REFTABLE_STACK_H
11
+
12
+#include "reftable-writer.h"
13
+
14
+/*
15
+ * The stack presents an interface to a mutable sequence of reftables.
16
+
17
+ * A stack can be mutated by pushing a table to the top of the stack.
18
+
19
+ * The reftable_stack automatically compacts files on disk to ensure good
20
+ * amortized performance.
21
+ *
22
+ * For windows and other platforms that cannot have open files as rename
23
+ * destinations, concurrent access from multiple processes needs the rand()
24
+ * random seed to be randomized.
25
+ */
26
+struct reftable_stack;
27
+
28
+/* open a new reftable stack. The tables along with the table list will be
29
+ * stored in 'dir'. Typically, this should be .git/reftables.
30
+ */
31
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
32
+ struct reftable_write_options config);
33
+
34
+/* returns the update_index at which a next table should be written. */
35
+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
36
+
37
+/* holds a transaction to add tables at the top of a stack. */
38
+struct reftable_addition;
39
+
40
+/*
41
+ * returns a new transaction to add reftables to the given stack. As a side
42
+ * effect, the ref database is locked.
43
+ */
44
+int reftable_stack_new_addition(struct reftable_addition **dest,
45
+ struct reftable_stack *st);
46
+
47
+/* Adds a reftable to transaction. */
48
+int reftable_addition_add(struct reftable_addition *add,
49
+ int (*write_table)(struct reftable_writer *wr,
50
+ void *arg),
51
+ void *arg);
52
+
53
+/* Commits the transaction, releasing the lock. After calling this,
54
+ * reftable_addition_destroy should still be called.
55
+ */
56
+int reftable_addition_commit(struct reftable_addition *add);
57
+
58
+/* Release all non-committed data from the transaction, and deallocate the
59
+ * transaction. Releases the lock if held. */
60
+void reftable_addition_destroy(struct reftable_addition *add);
61
+
62
+/* add a new table to the stack. The write_table function must call
63
+ * reftable_writer_set_limits, add refs and return an error value. */
64
+int reftable_stack_add(struct reftable_stack *st,
65
+ int (*write_table)(struct reftable_writer *wr,
66
+ void *write_arg),
67
+ void *write_arg);
68
+
69
+/* returns the merged_table for seeking. This table is valid until the
70
+ * next write or reload, and should not be closed or deleted.
71
+ */
72
+struct reftable_merged_table *
73
+reftable_stack_merged_table(struct reftable_stack *st);
74
+
75
+/* frees all resources associated with the stack. */
76
+void reftable_stack_destroy(struct reftable_stack *st);
77
+
78
+/* Reloads the stack if necessary. This is very cheap to run if the stack was up
79
+ * to date */
80
+int reftable_stack_reload(struct reftable_stack *st);
81
+
82
+/* Policy for expiring reflog entries. */
83
+struct reftable_log_expiry_config {
84
+ /* Drop entries older than this timestamp */
85
+ uint64_t time;
86
+
87
+ /* Drop older entries */
88
+ uint64_t min_update_index;
89
+};
90
+
91
+/* compacts all reftables into a giant table. Expire reflog entries if config is
92
+ * non-NULL */
93
+int reftable_stack_compact_all(struct reftable_stack *st,
94
+ struct reftable_log_expiry_config *config);
95
+
96
+/* heuristically compact unbalanced table stack. */
97
+int reftable_stack_auto_compact(struct reftable_stack *st);
98
+
99
+/* delete stale .ref tables. */
100
+int reftable_stack_clean(struct reftable_stack *st);
101
+
102
+/* convenience function to read a single ref. Returns < 0 for error, 0 for
103
+ * success, and 1 if ref not found. */
104
+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
105
+ struct reftable_ref_record *ref);
106
+
107
+/* convenience function to read a single log. Returns < 0 for error, 0 for
108
+ * success, and 1 if ref not found. */
109
+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
110
+ struct reftable_log_record *log);
111
+
112
+/* statistics on past compactions. */
113
+struct reftable_compaction_stats {
114
+ uint64_t bytes; /* total number of bytes written */
115
+ uint64_t entries_written; /* total number of entries written, including
116
+ failures. */
117
+ int attempts; /* how often we tried to compact */
118
+ int failures; /* failures happen on concurrent updates */
119
+};
120
+
121
+/* return statistics for compaction up till now. */
122
+struct reftable_compaction_stats *
123
+reftable_stack_compaction_stats(struct reftable_stack *st);
124
+
125
+/* print the entire stack represented by the directory */
126
+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);
127
+
128
+#endif
reftable/stack.c
new
+1396
@@ -0,0 +1,1396 @@
1
+/*
2
+Copyright 2020 Google LLC
3
+
4
+Use of this source code is governed by a BSD-style
5
+license that can be found in the LICENSE file or at
6
+https://developers.google.com/open-source/licenses/bsd
7
+*/
8
+
9
+#include "stack.h"
10
+
11
+#include "system.h"
12
+#include "merged.h"
13
+#include "reader.h"
14
+#include "refname.h"
15
+#include "reftable-error.h"
16
+#include "reftable-record.h"
17
+#include "reftable-merged.h"
18
+#include "writer.h"
19
+
20
+static int stack_try_add(struct reftable_stack *st,
21
+ int (*write_table)(struct reftable_writer *wr,
22
+ void *arg),
23
+ void *arg);
24
+static int stack_write_compact(struct reftable_stack *st,
25
+ struct reftable_writer *wr, int first, int last,
26
+ struct reftable_log_expiry_config *config);
27
+static int stack_check_addition(struct reftable_stack *st,
28
+ const char *new_tab_name);
29
+static void reftable_addition_close(struct reftable_addition *add);
30
+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
31
+ int reuse_open);
32
+
33
+static void stack_filename(struct strbuf *dest, struct reftable_stack *st,
34
+ const char *name)
35
+{
36
+ strbuf_reset(dest);
37
+ strbuf_addstr(dest, st->reftable_dir);
38
+ strbuf_addstr(dest, "/");
39
+ strbuf_addstr(dest, name);
40
+}
41
+
42
+static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)
43
+{
44
+ int *fdp = (int *)arg;
45
+ return write(*fdp, data, sz);
46
+}
47
+
48
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
49
+ struct reftable_write_options config)
50
+{
51
+ struct reftable_stack *p =
52
+ reftable_calloc(sizeof(struct reftable_stack));
53
+ struct strbuf list_file_name = STRBUF_INIT;
54
+ int err = 0;
55
+
56
+ if (config.hash_id == 0) {
57
+ config.hash_id = GIT_SHA1_FORMAT_ID;
58
+ }
59
+
60
+ *dest = NULL;
61
+
62
+ strbuf_reset(&list_file_name);
63
+ strbuf_addstr(&list_file_name, dir);
64
+ strbuf_addstr(&list_file_name, "/tables.list");
65
+
66
+ p->list_file = strbuf_detach(&list_file_name, NULL);
67
+ p->reftable_dir = xstrdup(dir);
68
+ p->config = config;
69
+
70
+ err = reftable_stack_reload_maybe_reuse(p, 1);
71
+ if (err < 0) {
72
+ reftable_stack_destroy(p);
73
+ } else {
74
+ *dest = p;
75
+ }
76
+ return err;
77
+}
78
+
79
+static int fd_read_lines(int fd, char ***namesp)
80
+{
81
+ off_t size = lseek(fd, 0, SEEK_END);
82
+ char *buf = NULL;
83
+ int err = 0;
84
+ if (size < 0) {
85
+ err = REFTABLE_IO_ERROR;
86
+ goto done;
87
+ }
88
+ err = lseek(fd, 0, SEEK_SET);
89
+ if (err < 0) {
90
+ err = REFTABLE_IO_ERROR;
91
+ goto done;
92
+ }
93
+
94
+ buf = reftable_malloc(size + 1);
95
+ if (read(fd, buf, size) != size) {
96
+ err = REFTABLE_IO_ERROR;
97
+ goto done;
98
+ }
99
+ buf[size] = 0;
100
+
101
+ parse_names(buf, size, namesp);
102
+
103
+done:
104
+ reftable_free(buf);
105
+ return err;
106
+}
107
+
108
+int read_lines(const char *filename, char ***namesp)
109
+{
110
+ int fd = open(filename, O_RDONLY);
111
+ int err = 0;
112
+ if (fd < 0) {
113
+ if (errno == ENOENT) {
114
+ *namesp = reftable_calloc(sizeof(char *));
115
+ return 0;
116
+ }
117
+
118
+ return REFTABLE_IO_ERROR;
119
+ }
120
+ err = fd_read_lines(fd, namesp);
121
+ close(fd);
122
+ return err;
123
+}
124
+
125
+struct reftable_merged_table *
126
+reftable_stack_merged_table(struct reftable_stack *st)
127
+{
128
+ return st->merged;
129
+}
130
+
131
+static int has_name(char **names, const char *name)
132
+{
133
+ while (*names) {
134
+ if (!strcmp(*names, name))
135
+ return 1;
136
+ names++;
137
+ }
138
+ return 0;
139
+}
140
+
141
+/* Close and free the stack */
142
+void reftable_stack_destroy(struct reftable_stack *st)
143
+{
144
+ char **names = NULL;
145
+ int err = 0;
146
+ if (st->merged) {
147
+ reftable_merged_table_free(st->merged);
148
+ st->merged = NULL;
149
+ }
150
+
151
+ err = read_lines(st->list_file, &names);
152
+ if (err < 0) {
153
+ FREE_AND_NULL(names);
154
+ }
155
+
156
+ if (st->readers) {
157
+ int i = 0;
158
+ struct strbuf filename = STRBUF_INIT;
159
+ for (i = 0; i < st->readers_len; i++) {
160
+ const char *name = reader_name(st->readers[i]);
161
+ strbuf_reset(&filename);
162
+ if (names && !has_name(names, name)) {
163
+ stack_filename(&filename, st, name);
164
+ }
165
+ reftable_reader_free(st->readers[i]);
166
+
167
+ if (filename.len) {
168
+ /* On Windows, can only unlink after closing. */
169
+ unlink(filename.buf);
170
+ }
171
+ }
172
+ strbuf_release(&filename);
173
+ st->readers_len = 0;
174
+ FREE_AND_NULL(st->readers);
175
+ }
176
+ FREE_AND_NULL(st->list_file);
177
+ FREE_AND_NULL(st->reftable_dir);
178
+ reftable_free(st);
179
+ free_names(names);
180
+}
181
+
182
+static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
183
+ int cur_len)
184
+{
185
+ struct reftable_reader **cur =
186
+ reftable_calloc(sizeof(struct reftable_reader *) * cur_len);
187
+ int i = 0;
188
+ for (i = 0; i < cur_len; i++) {
189
+ cur[i] = st->readers[i];
190
+ }
191
+ return cur;
192
+}
193
+
194
+static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
195
+ int reuse_open)
196
+{
197
+ int cur_len = !st->merged ? 0 : st->merged->stack_len;
198
+ struct reftable_reader **cur = stack_copy_readers(st, cur_len);
199
+ int err = 0;
200
+ int names_len = names_length(names);
201
+ struct reftable_reader **new_readers =
202
+ reftable_calloc(sizeof(struct reftable_reader *) * names_len);
203
+ struct reftable_table *new_tables =
204
+ reftable_calloc(sizeof(struct reftable_table) * names_len);
205
+ int new_readers_len = 0;
206
+ struct reftable_merged_table *new_merged = NULL;
207
+ int i;
208
+
209
+ while (*names) {
210
+ struct reftable_reader *rd = NULL;
211
+ char *name = *names++;
212
+
213
+ /* this is linear; we assume compaction keeps the number of
214
+ tables under control so this is not quadratic. */
215
+ int j = 0;
216
+ for (j = 0; reuse_open && j < cur_len; j++) {
217
+ if (cur[j] && 0 == strcmp(cur[j]->name, name)) {
218
+ rd = cur[j];
219
+ cur[j] = NULL;
220
+ break;
221
+ }
222
+ }
223
+
224
+ if (!rd) {
225
+ struct reftable_block_source src = { NULL };
226
+ struct strbuf table_path = STRBUF_INIT;
227
+ stack_filename(&table_path, st, name);
228
+
229
+ err = reftable_block_source_from_file(&src,
230
+ table_path.buf);
231
+ strbuf_release(&table_path);
232
+
233
+ if (err < 0)
234
+ goto done;
235
+
236
+ err = reftable_new_reader(&rd, &src, name);
237
+ if (err < 0)
238
+ goto done;
239
+ }
240
+
241
+ new_readers[new_readers_len] = rd;
242
+ reftable_table_from_reader(&new_tables[new_readers_len], rd);
243
+ new_readers_len++;
244
+ }
245
+
246
+ /* success! */
247
+ err = reftable_new_merged_table(&new_merged, new_tables,
248
+ new_readers_len, st->config.hash_id);
249
+ if (err < 0)
250
+ goto done;
251
+
252
+ new_tables = NULL;
253
+ st->readers_len = new_readers_len;
254
+ if (st->merged) {
255
+ merged_table_release(st->merged);
256
+ reftable_merged_table_free(st->merged);
257
+ }
258
+ if (st->readers) {
259
+ reftable_free(st->readers);
260
+ }
261
+ st->readers = new_readers;
262
+ new_readers = NULL;
263
+ new_readers_len = 0;
264
+
265
+ new_merged->suppress_deletions = 1;
266
+ st->merged = new_merged;
267
+ for (i = 0; i < cur_len; i++) {
268
+ if (cur[i]) {
269
+ const char *name = reader_name(cur[i]);
270
+ struct strbuf filename = STRBUF_INIT;
271
+ stack_filename(&filename, st, name);
272
+
273
+ reader_close(cur[i]);
274
+ reftable_reader_free(cur[i]);
275
+
276
+ /* On Windows, can only unlink after closing. */
277
+ unlink(filename.buf);
278
+
279
+ strbuf_release(&filename);
280
+ }
281
+ }
282
+
283
+done:
284
+ for (i = 0; i < new_readers_len; i++) {
285
+ reader_close(new_readers[i]);
286
+ reftable_reader_free(new_readers[i]);
287
+ }
288
+ reftable_free(new_readers);
289
+ reftable_free(new_tables);
290
+ reftable_free(cur);
291
+ return err;
292
+}
293
+
294
+/* return negative if a before b. */
295
+static int tv_cmp(struct timeval *a, struct timeval *b)
296
+{
297
+ time_t diff = a->tv_sec - b->tv_sec;
298
+ int udiff = a->tv_usec - b->tv_usec;
299
+
300
+ if (diff != 0)
301
+ return diff;
302
+
303
+ return udiff;
304
+}
305
+
306
+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
307
+ int reuse_open)
308
+{
309
+ struct timeval deadline = { 0 };
310
+ int err = gettimeofday(&deadline, NULL);
311
+ int64_t delay = 0;
312
+ int tries = 0;
313
+ if (err < 0)
314
+ return err;
315
+
316
+ deadline.tv_sec += 3;
317
+ while (1) {
318
+ char **names = NULL;
319
+ char **names_after = NULL;
320
+ struct timeval now = { 0 };
321
+ int err = gettimeofday(&now, NULL);
322
+ int err2 = 0;
323
+ if (err < 0) {
324
+ return err;
325
+ }
326
+
327
+ /* Only look at deadlines after the first few times. This
328
+ simplifies debugging in GDB */
329
+ tries++;
330
+ if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
331
+ break;
332
+ }
333
+
334
+ err = read_lines(st->list_file, &names);
335
+ if (err < 0) {
336
+ free_names(names);
337
+ return err;
338
+ }
339
+ err = reftable_stack_reload_once(st, names, reuse_open);
340
+ if (err == 0) {
341
+ free_names(names);
342
+ break;
343
+ }
344
+ if (err != REFTABLE_NOT_EXIST_ERROR) {
345
+ free_names(names);
346
+ return err;
347
+ }
348
+
349
+ /* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
350
+ writer. Check if there was one by checking if the name list
351
+ changed.
352
+ */
353
+ err2 = read_lines(st->list_file, &names_after);
354
+ if (err2 < 0) {
355
+ free_names(names);
356
+ return err2;
357
+ }
358
+
359
+ if (names_equal(names_after, names)) {
360
+ free_names(names);
361
+ free_names(names_after);
362
+ return err;
363
+ }
364
+ free_names(names);
365
+ free_names(names_after);
366
+
367
+ delay = delay + (delay * rand()) / RAND_MAX + 1;
368
+ sleep_millisec(delay);
369
+ }
370
+
371
+ return 0;
372
+}
373
+
374
+/* -1 = error
375
+ 0 = up to date
376
+ 1 = changed. */
377
+static int stack_uptodate(struct reftable_stack *st)
378
+{
379
+ char **names = NULL;
380
+ int err = read_lines(st->list_file, &names);
381
+ int i = 0;
382
+ if (err < 0)
383
+ return err;
384
+
385
+ for (i = 0; i < st->readers_len; i++) {
386
+ if (!names[i]) {
387
+ err = 1;
388
+ goto done;
389
+ }
390
+
391
+ if (strcmp(st->readers[i]->name, names[i])) {
392
+ err = 1;
393
+ goto done;
394
+ }
395
+ }
396
+
397
+ if (names[st->merged->stack_len]) {
398
+ err = 1;
399
+ goto done;
400
+ }
401
+
402
+done:
403
+ free_names(names);
404
+ return err;
405
+}
406
+
407
+int reftable_stack_reload(struct reftable_stack *st)
408
+{
409
+ int err = stack_uptodate(st);
410
+ if (err > 0)
411
+ return reftable_stack_reload_maybe_reuse(st, 1);
412
+ return err;
413
+}
414
+
415
+int reftable_stack_add(struct reftable_stack *st,
416
+ int (*write)(struct reftable_writer *wr, void *arg),
417
+ void *arg)
418
+{
419
+ int err = stack_try_add(st, write, arg);
420
+ if (err < 0) {
421
+ if (err == REFTABLE_LOCK_ERROR) {
422
+ /* Ignore error return, we want to propagate
423
+ REFTABLE_LOCK_ERROR.
424
+ */
425
+ reftable_stack_reload(st);
426
+ }
427
+ return err;
428
+ }
429
+
430
+ if (!st->disable_auto_compact)
431
+ return reftable_stack_auto_compact(st);
432
+
433
+ return 0;
434
+}
435
+
436
+static void format_name(struct strbuf *dest, uint64_t min, uint64_t max)
437
+{
438
+ char buf[100];
439
+ uint32_t rnd = (uint32_t)rand();
440
+ snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
441
+ min, max, rnd);
442
+ strbuf_reset(dest);
443
+ strbuf_addstr(dest, buf);
444
+}
445
+
446
+struct reftable_addition {
447
+ int lock_file_fd;
448
+ struct strbuf lock_file_name;
449
+ struct reftable_stack *stack;
450
+
451
+ char **new_tables;
452
+ int new_tables_len;
453
+ uint64_t next_update_index;
454
+};
455
+
456
+#define REFTABLE_ADDITION_INIT \
457
+ { \
458
+ .lock_file_name = STRBUF_INIT \
459
+ }
460
+
461
+static int reftable_stack_init_addition(struct reftable_addition *add,
462
+ struct reftable_stack *st)
463
+{
464
+ int err = 0;
465
+ add->stack = st;
466
+
467
+ strbuf_reset(&add->lock_file_name);
468
+ strbuf_addstr(&add->lock_file_name, st->list_file);
469
+ strbuf_addstr(&add->lock_file_name, ".lock");
470
+
471
+ add->lock_file_fd = open(add->lock_file_name.buf,
472
+ O_EXCL | O_CREAT | O_WRONLY, 0644);
473
+ if (add->lock_file_fd < 0) {
474
+ if (errno == EEXIST) {
475
+ err = REFTABLE_LOCK_ERROR;
476
+ } else {
477
+ err = REFTABLE_IO_ERROR;
478
+ }
479
+ goto done;
480
+ }
481
+ err = stack_uptodate(st);
482
+ if (err < 0)
483
+ goto done;
484
+
485
+ if (err > 1) {
486
+ err = REFTABLE_LOCK_ERROR;
487
+ goto done;
488
+ }
489
+
490
+ add->next_update_index = reftable_stack_next_update_index(st);
491
+done:
492
+ if (err) {
493
+ reftable_addition_close(add);
494
+ }
495
+ return err;
496
+}
497
+
498
+static void reftable_addition_close(struct reftable_addition *add)
499
+{
500
+ int i = 0;
501
+ struct strbuf nm = STRBUF_INIT;
502
+ for (i = 0; i < add->new_tables_len; i++) {
503
+ stack_filename(&nm, add->stack, add->new_tables[i]);
504
+ unlink(nm.buf);
505
+ reftable_free(add->new_tables[i]);
506
+ add->new_tables[i] = NULL;
507
+ }
508
+ reftable_free(add->new_tables);
509
+ add->new_tables = NULL;
510
+ add->new_tables_len = 0;
511
+
512
+ if (add->lock_file_fd > 0) {
513
+ close(add->lock_file_fd);
514
+ add->lock_file_fd = 0;
515
+ }
516
+ if (add->lock_file_name.len > 0) {
517
+ unlink(add->lock_file_name.buf);
518
+ strbuf_release(&add->lock_file_name);
519
+ }
520
+
521
+ strbuf_release(&nm);
522
+}
523
+
524
+void reftable_addition_destroy(struct reftable_addition *add)
525
+{
526
+ if (!add) {
527
+ return;
528
+ }
529
+ reftable_addition_close(add);
530
+ reftable_free(add);
531
+}
532
+
533
+int reftable_addition_commit(struct reftable_addition *add)
534
+{
535
+ struct strbuf table_list = STRBUF_INIT;
536
+ int i = 0;
537
+ int err = 0;
538
+ if (add->new_tables_len == 0)
539
+ goto done;
540
+
541
+ for (i = 0; i < add->stack->merged->stack_len; i++) {
542
+ strbuf_addstr(&table_list, add->stack->readers[i]->name);
543
+ strbuf_addstr(&table_list, "\n");
544
+ }
545
+ for (i = 0; i < add->new_tables_len; i++) {
546
+ strbuf_addstr(&table_list, add->new_tables[i]);
547
+ strbuf_addstr(&table_list, "\n");
548
+ }
549
+
550
+ err = write(add->lock_file_fd, table_list.buf, table_list.len);
551
+ strbuf_release(&table_list);
552
+ if (err < 0) {
553
+ err = REFTABLE_IO_ERROR;
554
+ goto done;
555
+ }
556
+
557
+ err = close(add->lock_file_fd);
558
+ add->lock_file_fd = 0;
559
+ if (err < 0) {
560
+ err = REFTABLE_IO_ERROR;
561
+ goto done;
562
+ }
563
+
564
+ err = rename(add->lock_file_name.buf, add->stack->list_file);
565
+ if (err < 0) {
566
+ err = REFTABLE_IO_ERROR;
567
+ goto done;
568
+ }
569
+
570
+ /* success, no more state to clean up. */
571
+ strbuf_release(&add->lock_file_name);
572
+ for (i = 0; i < add->new_tables_len; i++) {
573
+ reftable_free(add->new_tables[i]);
574
+ }
575
+ reftable_free(add->new_tables);
576
+ add->new_tables = NULL;
577
+ add->new_tables_len = 0;
578
+
579
+ err = reftable_stack_reload(add->stack);
580
+done:
581
+ reftable_addition_close(add);
582
+ return err;
583
+}
584
+
585
+int reftable_stack_new_addition(struct reftable_addition **dest,
586
+ struct reftable_stack *st)
587
+{
588
+ int err = 0;
589
+ struct reftable_addition empty = REFTABLE_ADDITION_INIT;
590
+ *dest = reftable_calloc(sizeof(**dest));
591
+ **dest = empty;
592
+ err = reftable_stack_init_addition(*dest, st);
593
+ if (err) {
594
+ reftable_free(*dest);
595
+ *dest = NULL;
596
+ }
597
+ return err;
598
+}
599
+
600
+static int stack_try_add(struct reftable_stack *st,
601
+ int (*write_table)(struct reftable_writer *wr,
602
+ void *arg),
603
+ void *arg)
604
+{
605
+ struct reftable_addition add = REFTABLE_ADDITION_INIT;
606
+ int err = reftable_stack_init_addition(&add, st);
607
+ if (err < 0)
608
+ goto done;
609
+ if (err > 0) {
610
+ err = REFTABLE_LOCK_ERROR;
611
+ goto done;
612
+ }
613
+
614
+ err = reftable_addition_add(&add, write_table, arg);
615
+ if (err < 0)
616
+ goto done;
617
+
618
+ err = reftable_addition_commit(&add);
619
+done:
620
+ reftable_addition_close(&add);
621
+ return err;
622
+}
623
+
624
+int reftable_addition_add(struct reftable_addition *add,
625
+ int (*write_table)(struct reftable_writer *wr,
626
+ void *arg),
627
+ void *arg)
628
+{
629
+ struct strbuf temp_tab_file_name = STRBUF_INIT;
630
+ struct strbuf tab_file_name = STRBUF_INIT;
631
+ struct strbuf next_name = STRBUF_INIT;
632
+ struct reftable_writer *wr = NULL;
633
+ int err = 0;
634
+ int tab_fd = 0;
635
+
636
+ strbuf_reset(&next_name);
637
+ format_name(&next_name, add->next_update_index, add->next_update_index);
638
+
639
+ stack_filename(&temp_tab_file_name, add->stack, next_name.buf);
640
+ strbuf_addstr(&temp_tab_file_name, ".temp.XXXXXX");
641
+
642
+ tab_fd = mkstemp(temp_tab_file_name.buf);
643
+ if (tab_fd < 0) {
644
+ err = REFTABLE_IO_ERROR;
645
+ goto done;
646
+ }
647
+
648
+ wr = reftable_new_writer(reftable_fd_write, &tab_fd,
649
+ &add->stack->config);
650
+ err = write_table(wr, arg);
651
+ if (err < 0)
652
+ goto done;
653
+
654
+ err = reftable_writer_close(wr);
655
+ if (err == REFTABLE_EMPTY_TABLE_ERROR) {
656
+ err = 0;
657
+ goto done;
658
+ }
659
+ if (err < 0)
660
+ goto done;
661
+
662
+ err = close(tab_fd);
663
+ tab_fd = 0;
664
+ if (err < 0) {
665
+ err = REFTABLE_IO_ERROR;
666
+ goto done;
667
+ }
668
+
669
+ err = stack_check_addition(add->stack, temp_tab_file_name.buf);
670
+ if (err < 0)
671
+ goto done;
672
+
673
+ if (wr->min_update_index < add->next_update_index) {
674
+ err = REFTABLE_API_ERROR;
675
+ goto done;
676
+ }
677
+
678
+ format_name(&next_name, wr->min_update_index, wr->max_update_index);
679
+ strbuf_addstr(&next_name, ".ref");
680
+
681
+ stack_filename(&tab_file_name, add->stack, next_name.buf);
682
+
683
+ /*
684
+ On windows, this relies on rand() picking a unique destination name.
685
+ Maybe we should do retry loop as well?
686
+ */
687
+ err = rename(temp_tab_file_name.buf, tab_file_name.buf);
688
+ if (err < 0) {
689
+ err = REFTABLE_IO_ERROR;
690
+ goto done;
691
+ }
692
+
693
+ add->new_tables = reftable_realloc(add->new_tables,
694
+ sizeof(*add->new_tables) *
695
+ (add->new_tables_len + 1));
696
+ add->new_tables[add->new_tables_len] = strbuf_detach(&next_name, NULL);
697
+ add->new_tables_len++;
698
+done:
699
+ if (tab_fd > 0) {
700
+ close(tab_fd);
701
+ tab_fd = 0;
702
+ }
703
+ if (temp_tab_file_name.len > 0) {
704
+ unlink(temp_tab_file_name.buf);
705
+ }
706
+
707
+ strbuf_release(&temp_tab_file_name);
708
+ strbuf_release(&tab_file_name);
709
+ strbuf_release(&next_name);
710
+ reftable_writer_free(wr);
711
+ return err;
712
+}
713
+
714
+uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
715
+{
716
+ int sz = st->merged->stack_len;
717
+ if (sz > 0)
718
+ return reftable_reader_max_update_index(st->readers[sz - 1]) +
719
+ 1;
720
+ return 1;
721
+}
722
+
723
+static int stack_compact_locked(struct reftable_stack *st, int first, int last,
724
+ struct strbuf *temp_tab,
725
+ struct reftable_log_expiry_config *config)
726
+{
727
+ struct strbuf next_name = STRBUF_INIT;
728
+ int tab_fd = -1;
729
+ struct reftable_writer *wr = NULL;
730
+ int err = 0;
731
+
732
+ format_name(&next_name,
733
+ reftable_reader_min_update_index(st->readers[first]),
734
+ reftable_reader_max_update_index(st->readers[last]));
735
+
736
+ stack_filename(temp_tab, st, next_name.buf);
737
+ strbuf_addstr(temp_tab, ".temp.XXXXXX");
738
+
739
+ tab_fd = mkstemp(temp_tab->buf);
740
+ wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);
741
+
742
+ err = stack_write_compact(st, wr, first, last, config);
743
+ if (err < 0)
744
+ goto done;
745
+ err = reftable_writer_close(wr);
746
+ if (err < 0)
747
+ goto done;
748
+
749
+ err = close(tab_fd);
750
+ tab_fd = 0;
751
+
752
+done:
753
+ reftable_writer_free(wr);
754
+ if (tab_fd > 0) {
755
+ close(tab_fd);
756
+ tab_fd = 0;
757
+ }
758
+ if (err != 0 && temp_tab->len > 0) {
759
+ unlink(temp_tab->buf);
760
+ strbuf_release(temp_tab);
761
+ }
762
+ strbuf_release(&next_name);
763
+ return err;
764
+}
765
+
766
+static int stack_write_compact(struct reftable_stack *st,
767
+ struct reftable_writer *wr, int first, int last,
768
+ struct reftable_log_expiry_config *config)
769
+{
770
+ int subtabs_len = last - first + 1;
771
+ struct reftable_table *subtabs = reftable_calloc(
772
+ sizeof(struct reftable_table) * (last - first + 1));
773
+ struct reftable_merged_table *mt = NULL;
774
+ int err = 0;
775
+ struct reftable_iterator it = { NULL };
776
+ struct reftable_ref_record ref = { NULL };
777
+ struct reftable_log_record log = { NULL };
778
+
779
+ uint64_t entries = 0;
780
+
781
+ int i = 0, j = 0;
782
+ for (i = first, j = 0; i <= last; i++) {
783
+ struct reftable_reader *t = st->readers[i];
784
+ reftable_table_from_reader(&subtabs[j++], t);
785
+ st->stats.bytes += t->size;
786
+ }
787
+ reftable_writer_set_limits(wr, st->readers[first]->min_update_index,
788
+ st->readers[last]->max_update_index);
789
+
790
+ err = reftable_new_merged_table(&mt, subtabs, subtabs_len,
791
+ st->config.hash_id);
792
+ if (err < 0) {
793
+ reftable_free(subtabs);
794
+ goto done;
795
+ }
796
+
797
+ err = reftable_merged_table_seek_ref(mt, &it, "");
798
+ if (err < 0)
799
+ goto done;
800
+
801
+ while (1) {
802
+ err = reftable_iterator_next_ref(&it, &ref);
803
+ if (err > 0) {
804
+ err = 0;
805
+ break;
806
+ }
807
+ if (err < 0) {
808
+ break;
809
+ }
810
+
811
+ if (first == 0 && reftable_ref_record_is_deletion(&ref)) {
812
+ continue;
813
+ }
814
+
815
+ err = reftable_writer_add_ref(wr, &ref);
816
+ if (err < 0) {
817
+ break;
818
+ }
819
+ entries++;
820
+ }
821
+ reftable_iterator_destroy(&it);
822
+
823
+ err = reftable_merged_table_seek_log(mt, &it, "");
824
+ if (err < 0)
825
+ goto done;
826
+
827
+ while (1) {
828
+ err = reftable_iterator_next_log(&it, &log);
829
+ if (err > 0) {
830
+ err = 0;
831
+ break;
832
+ }
833
+ if (err < 0) {
834
+ break;
835
+ }
836
+ if (first == 0 && reftable_log_record_is_deletion(&log)) {
837
+ continue;
838
+ }
839
+
840
+ if (config && config->min_update_index > 0 &&
841
+ log.update_index < config->min_update_index) {
842
+ continue;
843
+ }
844
+
845
+ if (config && config->time > 0 &&
846
+ log.value.update.time < config->time) {
847
+ continue;
848
+ }
849
+
850
+ err = reftable_writer_add_log(wr, &log);
851
+ if (err < 0) {
852
+ break;
853
+ }
854
+ entries++;
855
+ }
856
+
857
+done:
858
+ reftable_iterator_destroy(&it);
859
+ if (mt) {
860
+ merged_table_release(mt);
861
+ reftable_merged_table_free(mt);
862
+ }
863
+ reftable_ref_record_release(&ref);
864
+ reftable_log_record_release(&log);
865
+ st->stats.entries_written += entries;
866
+ return err;
867
+}
868
+
869
+/* < 0: error. 0 == OK, > 0 attempt failed; could retry. */
870
+static int stack_compact_range(struct reftable_stack *st, int first, int last,
871
+ struct reftable_log_expiry_config *expiry)
872
+{
873
+ struct strbuf temp_tab_file_name = STRBUF_INIT;
874
+ struct strbuf new_table_name = STRBUF_INIT;
875
+ struct strbuf lock_file_name = STRBUF_INIT;
876
+ struct strbuf ref_list_contents = STRBUF_INIT;
877
+ struct strbuf new_table_path = STRBUF_INIT;
878
+ int err = 0;
879
+ int have_lock = 0;
880
+ int lock_file_fd = 0;
881
+ int compact_count = last - first + 1;
882
+ char **listp = NULL;
883
+ char **delete_on_success =
884
+ reftable_calloc(sizeof(char *) * (compact_count + 1));
885
+ char **subtable_locks =
886
+ reftable_calloc(sizeof(char *) * (compact_count + 1));
887
+ int i = 0;
888
+ int j = 0;
889
+ int is_empty_table = 0;
890
+
891
+ if (first > last || (!expiry && first == last)) {
892
+ err = 0;
893
+ goto done;
894
+ }
895
+
896
+ st->stats.attempts++;
897
+
898
+ strbuf_reset(&lock_file_name);
899
+ strbuf_addstr(&lock_file_name, st->list_file);
900
+ strbuf_addstr(&lock_file_name, ".lock");
901
+
902
+ lock_file_fd =
903
+ open(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);
904
+ if (lock_file_fd < 0) {
905
+ if (errno == EEXIST) {
906
+ err = 1;
907
+ } else {
908
+ err = REFTABLE_IO_ERROR;
909
+ }
910
+ goto done;
911
+ }
912
+ /* Don't want to write to the lock for now. */
913
+ close(lock_file_fd);
914
+ lock_file_fd = 0;
915
+
916
+ have_lock = 1;
917
+ err = stack_uptodate(st);
918
+ if (err != 0)
919
+ goto done;
920
+
921
+ for (i = first, j = 0; i <= last; i++) {
922
+ struct strbuf subtab_file_name = STRBUF_INIT;
923
+ struct strbuf subtab_lock = STRBUF_INIT;
924
+ int sublock_file_fd = -1;
925
+
926
+ stack_filename(&subtab_file_name, st,
927
+ reader_name(st->readers[i]));
928
+
929
+ strbuf_reset(&subtab_lock);
930
+ strbuf_addbuf(&subtab_lock, &subtab_file_name);
931
+ strbuf_addstr(&subtab_lock, ".lock");
932
+
933
+ sublock_file_fd = open(subtab_lock.buf,
934
+ O_EXCL | O_CREAT | O_WRONLY, 0644);
935
+ if (sublock_file_fd > 0) {
936
+ close(sublock_file_fd);
937
+ } else if (sublock_file_fd < 0) {
938
+ if (errno == EEXIST) {
939
+ err = 1;
940
+ } else {
941
+ err = REFTABLE_IO_ERROR;
942
+ }
943
+ }
944
+
945
+ subtable_locks[j] = subtab_lock.buf;
946
+ delete_on_success[j] = subtab_file_name.buf;
947
+ j++;
948
+
949
+ if (err != 0)
950
+ goto done;
951
+ }
952
+
953
+ err = unlink(lock_file_name.buf);
954
+ if (err < 0)
955
+ goto done;
956
+ have_lock = 0;
957
+
958
+ err = stack_compact_locked(st, first, last, &temp_tab_file_name,
959
+ expiry);
960
+ /* Compaction + tombstones can create an empty table out of non-empty
961
+ * tables. */
962
+ is_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);
963
+ if (is_empty_table) {
964
+ err = 0;
965
+ }
966
+ if (err < 0)
967
+ goto done;
968
+
969
+ lock_file_fd =
970
+ open(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);
971
+ if (lock_file_fd < 0) {
972
+ if (errno == EEXIST) {
973
+ err = 1;
974
+ } else {
975
+ err = REFTABLE_IO_ERROR;
976
+ }
977
+ goto done;
978
+ }
979
+ have_lock = 1;
980
+
981
+ format_name(&new_table_name, st->readers[first]->min_update_index,
982
+ st->readers[last]->max_update_index);
983
+ strbuf_addstr(&new_table_name, ".ref");
984
+
985
+ stack_filename(&new_table_path, st, new_table_name.buf);
986
+
987
+ if (!is_empty_table) {
988
+ /* retry? */
989
+ err = rename(temp_tab_file_name.buf, new_table_path.buf);
990
+ if (err < 0) {
991
+ err = REFTABLE_IO_ERROR;
992
+ goto done;
993
+ }
994
+ }
995
+
996
+ for (i = 0; i < first; i++) {
997
+ strbuf_addstr(&ref_list_contents, st->readers[i]->name);
998
+ strbuf_addstr(&ref_list_contents, "\n");
999
+ }
1000
+ if (!is_empty_table) {
1001
+ strbuf_addbuf(&ref_list_contents, &new_table_name);
1002
+ strbuf_addstr(&ref_list_contents, "\n");
1003
+ }
1004
+ for (i = last + 1; i < st->merged->stack_len; i++) {
1005
+ strbuf_addstr(&ref_list_contents, st->readers[i]->name);
1006
+ strbuf_addstr(&ref_list_contents, "\n");
1007
+ }
1008
+
1009
+ err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
1010
+ if (err < 0) {
1011
+ err = REFTABLE_IO_ERROR;
1012
+ unlink(new_table_path.buf);
1013
+ goto done;
1014
+ }
1015
+ err = close(lock_file_fd);
1016
+ lock_file_fd = 0;
1017
+ if (err < 0) {
1018
+ err = REFTABLE_IO_ERROR;
1019
+ unlink(new_table_path.buf);
1020
+ goto done;
1021
+ }
1022
+
1023
+ err = rename(lock_file_name.buf, st->list_file);
1024
+ if (err < 0) {
1025
+ err = REFTABLE_IO_ERROR;
1026
+ unlink(new_table_path.buf);
1027
+ goto done;
1028
+ }
1029
+ have_lock = 0;
1030
+
1031
+ /* Reload the stack before deleting. On windows, we can only delete the
1032
+ files after we closed them.
1033
+ */
1034
+ err = reftable_stack_reload_maybe_reuse(st, first < last);
1035
+
1036
+ listp = delete_on_success;
1037
+ while (*listp) {
1038
+ if (strcmp(*listp, new_table_path.buf)) {
1039
+ unlink(*listp);
1040
+ }
1041
+ listp++;
1042
+ }
1043
+
1044
+done:
1045
+ free_names(delete_on_success);
1046
+
1047
+ listp = subtable_locks;
1048
+ while (*listp) {
1049
+ unlink(*listp);
1050
+ listp++;
1051
+ }
1052
+ free_names(subtable_locks);
1053
+ if (lock_file_fd > 0) {
1054
+ close(lock_file_fd);
1055
+ lock_file_fd = 0;
1056
+ }
1057
+ if (have_lock) {
1058
+ unlink(lock_file_name.buf);
1059
+ }
1060
+ strbuf_release(&new_table_name);
1061
+ strbuf_release(&new_table_path);
1062
+ strbuf_release(&ref_list_contents);
1063
+ strbuf_release(&temp_tab_file_name);
1064
+ strbuf_release(&lock_file_name);
1065
+ return err;
1066
+}
1067
+
1068
+int reftable_stack_compact_all(struct reftable_stack *st,
1069
+ struct reftable_log_expiry_config *config)
1070
+{
1071
+ return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
1072
+}
1073
+
1074
+static int stack_compact_range_stats(struct reftable_stack *st, int first,
1075
+ int last,
1076
+ struct reftable_log_expiry_config *config)
1077
+{
1078
+ int err = stack_compact_range(st, first, last, config);
1079
+ if (err > 0) {
1080
+ st->stats.failures++;
1081
+ }
1082
+ return err;
1083
+}
1084
+
1085
+static int segment_size(struct segment *s)
1086
+{
1087
+ return s->end - s->start;
1088
+}
1089
+
1090
+int fastlog2(uint64_t sz)
1091
+{
1092
+ int l = 0;
1093
+ if (sz == 0)
1094
+ return 0;
1095
+ for (; sz; sz /= 2) {
1096
+ l++;
1097
+ }
1098
+ return l - 1;
1099
+}
1100
+
1101
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
1102
+{
1103
+ struct segment *segs = reftable_calloc(sizeof(struct segment) * n);
1104
+ int next = 0;
1105
+ struct segment cur = { 0 };
1106
+ int i = 0;
1107
+
1108
+ if (n == 0) {
1109
+ *seglen = 0;
1110
+ return segs;
1111
+ }
1112
+ for (i = 0; i < n; i++) {
1113
+ int log = fastlog2(sizes[i]);
1114
+ if (cur.log != log && cur.bytes > 0) {
1115
+ struct segment fresh = {
1116
+ .start = i,
1117
+ };
1118
+
1119
+ segs[next++] = cur;
1120
+ cur = fresh;
1121
+ }
1122
+
1123
+ cur.log = log;
1124
+ cur.end = i + 1;
1125
+ cur.bytes += sizes[i];
1126
+ }
1127
+ segs[next++] = cur;
1128
+ *seglen = next;
1129
+ return segs;
1130
+}
1131
+
1132
+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
1133
+{
1134
+ int seglen = 0;
1135
+ struct segment *segs = sizes_to_segments(&seglen, sizes, n);
1136
+ struct segment min_seg = {
1137
+ .log = 64,
1138
+ };
1139
+ int i = 0;
1140
+ for (i = 0; i < seglen; i++) {
1141
+ if (segment_size(&segs[i]) == 1) {
1142
+ continue;
1143
+ }
1144
+
1145
+ if (segs[i].log < min_seg.log) {
1146
+ min_seg = segs[i];
1147
+ }
1148
+ }
1149
+
1150
+ while (min_seg.start > 0) {
1151
+ int prev = min_seg.start - 1;
1152
+ if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
1153
+ break;
1154
+ }
1155
+
1156
+ min_seg.start = prev;
1157
+ min_seg.bytes += sizes[prev];
1158
+ }
1159
+
1160
+ reftable_free(segs);
1161
+ return min_seg;
1162
+}
1163
+
1164
+static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)
1165
+{
1166
+ uint64_t *sizes =
1167
+ reftable_calloc(sizeof(uint64_t) * st->merged->stack_len);
1168
+ int version = (st->config.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2;
1169
+ int overhead = header_size(version) - 1;
1170
+ int i = 0;
1171
+ for (i = 0; i < st->merged->stack_len; i++) {
1172
+ sizes[i] = st->readers[i]->size - overhead;
1173
+ }
1174
+ return sizes;
1175
+}
1176
+
1177
+int reftable_stack_auto_compact(struct reftable_stack *st)
1178
+{
1179
+ uint64_t *sizes = stack_table_sizes_for_compaction(st);
1180
+ struct segment seg =
1181
+ suggest_compaction_segment(sizes, st->merged->stack_len);
1182
+ reftable_free(sizes);
1183
+ if (segment_size(&seg) > 0)
1184
+ return stack_compact_range_stats(st, seg.start, seg.end - 1,
1185
+ NULL);
1186
+
1187
+ return 0;
1188
+}
1189
+
1190
+struct reftable_compaction_stats *
1191
+reftable_stack_compaction_stats(struct reftable_stack *st)
1192
+{
1193
+ return &st->stats;
1194
+}
1195
+
1196
+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
1197
+ struct reftable_ref_record *ref)
1198
+{
1199
+ struct reftable_table tab = { NULL };
1200
+ reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));
1201
+ return reftable_table_read_ref(&tab, refname, ref);
1202
+}
1203
+
1204
+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
1205
+ struct reftable_log_record *log)
1206
+{
1207
+ struct reftable_iterator it = { NULL };
1208
+ struct reftable_merged_table *mt = reftable_stack_merged_table(st);
1209
+ int err = reftable_merged_table_seek_log(mt, &it, refname);
1210
+ if (err)
1211
+ goto done;
1212
+
1213
+ err = reftable_iterator_next_log(&it, log);
1214
+ if (err)
1215
+ goto done;
1216
+
1217
+ if (strcmp(log->refname, refname) ||
1218
+ reftable_log_record_is_deletion(log)) {
1219
+ err = 1;
1220
+ goto done;
1221
+ }
1222
+
1223
+done:
1224
+ if (err) {
1225
+ reftable_log_record_release(log);
1226
+ }
1227
+ reftable_iterator_destroy(&it);
1228
+ return err;
1229
+}
1230
+
1231
+static int stack_check_addition(struct reftable_stack *st,
1232
+ const char *new_tab_name)
1233
+{
1234
+ int err = 0;
1235
+ struct reftable_block_source src = { NULL };
1236
+ struct reftable_reader *rd = NULL;
1237
+ struct reftable_table tab = { NULL };
1238
+ struct reftable_ref_record *refs = NULL;
1239
+ struct reftable_iterator it = { NULL };
1240
+ int cap = 0;
1241
+ int len = 0;
1242
+ int i = 0;
1243
+
1244
+ if (st->config.skip_name_check)
1245
+ return 0;
1246
+
1247
+ err = reftable_block_source_from_file(&src, new_tab_name);
1248
+ if (err < 0)
1249
+ goto done;
1250
+
1251
+ err = reftable_new_reader(&rd, &src, new_tab_name);
1252
+ if (err < 0)
1253
+ goto done;
1254
+
1255
+ err = reftable_reader_seek_ref(rd, &it, "");
1256
+ if (err > 0) {
1257
+ err = 0;
1258
+ goto done;
1259
+ }
1260
+ if (err < 0)
1261
+ goto done;
1262
+
1263
+ while (1) {
1264
+ struct reftable_ref_record ref = { NULL };
1265
+ err = reftable_iterator_next_ref(&it, &ref);
1266
+ if (err > 0) {
1267
+ break;
1268
+ }
1269
+ if (err < 0)
1270
+ goto done;
1271
+
1272
+ if (len >= cap) {
1273
+ cap = 2 * cap + 1;
1274
+ refs = reftable_realloc(refs, cap * sizeof(refs[0]));
1275
+ }
1276
+
1277
+ refs[len++] = ref;
1278
+ }
1279
+
1280
+ reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));
1281
+
1282
+ err = validate_ref_record_addition(tab, refs, len);
1283
+
1284
+done:
1285
+ for (i = 0; i < len; i++) {
1286
+ reftable_ref_record_release(&refs[i]);
1287
+ }
1288
+
1289
+ free(refs);
1290
+ reftable_iterator_destroy(&it);
1291
+ reftable_reader_free(rd);
1292
+ return err;
1293
+}
1294
+
1295
+static int is_table_name(const char *s)
1296
+{
1297
+ const char *dot = strrchr(s, '.');
1298
+ return dot && !strcmp(dot, ".ref");
1299
+}
1300
+
1301
+static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max,
1302
+ const char *name)
1303
+{
1304
+ int err = 0;
1305
+ uint64_t update_idx = 0;
1306
+ struct reftable_block_source src = { NULL };
1307
+ struct reftable_reader *rd = NULL;
1308
+ struct strbuf table_path = STRBUF_INIT;
1309
+ stack_filename(&table_path, st, name);
1310
+
1311
+ err = reftable_block_source_from_file(&src, table_path.buf);
1312
+ if (err < 0)
1313
+ goto done;
1314
+
1315
+ err = reftable_new_reader(&rd, &src, name);
1316
+ if (err < 0)
1317
+ goto done;
1318
+
1319
+ update_idx = reftable_reader_max_update_index(rd);
1320
+ reftable_reader_free(rd);
1321
+
1322
+ if (update_idx <= max) {
1323
+ unlink(table_path.buf);
1324
+ }
1325
+done:
1326
+ strbuf_release(&table_path);
1327
+}
1328
+
1329
+static int reftable_stack_clean_locked(struct reftable_stack *st)
1330
+{
1331
+ uint64_t max = reftable_merged_table_max_update_index(
1332
+ reftable_stack_merged_table(st));
1333
+ DIR *dir = opendir(st->reftable_dir);
1334
+ struct dirent *d = NULL;
1335
+ if (!dir) {
1336
+ return REFTABLE_IO_ERROR;
1337
+ }
1338
+
1339
+ while ((d = readdir(dir))) {
1340
+ int i = 0;
1341
+ int found = 0;
1342
+ if (!is_table_name(d->d_name))
1343
+ continue;
1344
+
1345
+ for (i = 0; !found && i < st->readers_len; i++) {
1346
+ found = !strcmp(reader_name(st->readers[i]), d->d_name);
1347
+ }
1348
+ if (found)
1349
+ continue;
1350
+
1351
+ remove_maybe_stale_table(st, max, d->d_name);
1352
+ }
1353
+
1354
+ closedir(dir);
1355
+ return 0;
1356
+}
1357
+
1358
+int reftable_stack_clean(struct reftable_stack *st)
1359
+{
1360
+ struct reftable_addition *add = NULL;
1361
+ int err = reftable_stack_new_addition(&add, st);
1362
+ if (err < 0) {
1363
+ goto done;
1364
+ }
1365
+
1366
+ err = reftable_stack_reload(st);
1367
+ if (err < 0) {
1368
+ goto done;
1369
+ }
1370
+
1371
+ err = reftable_stack_clean_locked(st);
1372
+
1373
+done:
1374
+ reftable_addition_destroy(add);
1375
+ return err;
1376
+}
1377
+
1378
+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id)
1379
+{
1380
+ struct reftable_stack *stack = NULL;
1381
+ struct reftable_write_options cfg = { .hash_id = hash_id };
1382
+ struct reftable_merged_table *merged = NULL;
1383
+ struct reftable_table table = { NULL };
1384
+
1385
+ int err = reftable_new_stack(&stack, stackdir, cfg);
1386
+ if (err < 0)
1387
+ goto done;
1388
+
1389
+ merged = reftable_stack_merged_table(stack);
1390
+ reftable_table_from_merged_table(&table, merged);
1391
+ err = reftable_table_print(&table);
1392
+done:
1393
+ if (stack)
1394
+ reftable_stack_destroy(stack);
1395
+ return err;
1396
+}
reftable/stack.h
new
+41
@@ -0,0 +1,41 @@
1
+/*
2
+Copyright 2020 Google LLC
3
+
4
+Use of this source code is governed by a BSD-style
5
+license that can be found in the LICENSE file or at
6
+https://developers.google.com/open-source/licenses/bsd
7
+*/
8
+
9
+#ifndef STACK_H
10
+#define STACK_H
11
+
12
+#include "system.h"
13
+#include "reftable-writer.h"
14
+#include "reftable-stack.h"
15
+
16
+struct reftable_stack {
17
+ char *list_file;
18
+ char *reftable_dir;
19
+ int disable_auto_compact;
20
+
21
+ struct reftable_write_options config;
22
+
23
+ struct reftable_reader **readers;
24
+ size_t readers_len;
25
+ struct reftable_merged_table *merged;
26
+ struct reftable_compaction_stats stats;
27
+};
28
+
29
+int read_lines(const char *filename, char ***lines);
30
+
31
+struct segment {
32
+ int start, end;
33
+ int log;
34
+ uint64_t bytes;
35
+};
36
+
37
+int fastlog2(uint64_t sz);
38
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
39
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
40
+
41
+#endif
reftable/stack_test.c
new
+953
@@ -0,0 +1,953 @@
1
+/*
2
+Copyright 2020 Google LLC
3
+
4
+Use of this source code is governed by a BSD-style
5
+license that can be found in the LICENSE file or at
6
+https://developers.google.com/open-source/licenses/bsd
7
+*/
8
+
9
+#include "stack.h"
10
+
11
+#include "system.h"
12
+
13
+#include "reftable-reader.h"
14
+#include "merged.h"
15
+#include "basics.h"
16
+#include "constants.h"
17
+#include "record.h"
18
+#include "test_framework.h"
19
+#include "reftable-tests.h"
20
+
21
+#include <sys/types.h>
22
+#include <dirent.h>
23
+
24
+static void clear_dir(const char *dirname)
25
+{
26
+ struct strbuf path = STRBUF_INIT;
27
+ strbuf_addstr(&path, dirname);
28
+ remove_dir_recursively(&path, 0);
29
+ strbuf_release(&path);
30
+}
31
+
32
+static int count_dir_entries(const char *dirname)
33
+{
34
+ DIR *dir = opendir(dirname);
35
+ int len = 0;
36
+ struct dirent *d;
37
+ if (dir == NULL)
38
+ return 0;
39
+
40
+ while ((d = readdir(dir))) {
41
+ if (!strcmp(d->d_name, "..") || !strcmp(d->d_name, "."))
42
+ continue;
43
+ len++;
44
+ }
45
+ closedir(dir);
46
+ return len;
47
+}
48
+
49
+/*
50
+ * Work linenumber into the tempdir, so we can see which tests forget to
51
+ * cleanup.
52
+ */
53
+static char *get_tmp_template(int linenumber)
54
+{
55
+ const char *tmp = getenv("TMPDIR");
56
+ static char template[1024];
57
+ snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
58
+ tmp ? tmp : "/tmp", linenumber);
59
+ return template;
60
+}
61
+
62
+static char *get_tmp_dir(int linenumber)
63
+{
64
+ char *dir = get_tmp_template(linenumber);
65
+ EXPECT(mkdtemp(dir));
66
+ return dir;
67
+}
68
+
69
+static void test_read_file(void)
70
+{
71
+ char *fn = get_tmp_template(__LINE__);
72
+ int fd = mkstemp(fn);
73
+ char out[1024] = "line1\n\nline2\nline3";
74
+ int n, err;
75
+ char **names = NULL;
76
+ char *want[] = { "line1", "line2", "line3" };
77
+ int i = 0;
78
+
79
+ EXPECT(fd > 0);
80
+ n = write(fd, out, strlen(out));
81
+ EXPECT(n == strlen(out));
82
+ err = close(fd);
83
+ EXPECT(err >= 0);
84
+
85
+ err = read_lines(fn, &names);
86
+ EXPECT_ERR(err);
87
+
88
+ for (i = 0; names[i]; i++) {
89
+ EXPECT(0 == strcmp(want[i], names[i]));
90
+ }
91
+ free_names(names);
92
+ remove(fn);
93
+}
94
+
95
+static void test_parse_names(void)
96
+{
97
+ char buf[] = "line\n";
98
+ char **names = NULL;
99
+ parse_names(buf, strlen(buf), &names);
100
+
101
+ EXPECT(NULL != names[0]);
102
+ EXPECT(0 == strcmp(names[0], "line"));
103
+ EXPECT(NULL == names[1]);
104
+ free_names(names);
105
+}
106
+
107
+static void test_names_equal(void)
108
+{
109
+ char *a[] = { "a", "b", "c", NULL };
110
+ char *b[] = { "a", "b", "d", NULL };
111
+ char *c[] = { "a", "b", NULL };
112
+
113
+ EXPECT(names_equal(a, a));
114
+ EXPECT(!names_equal(a, b));
115
+ EXPECT(!names_equal(a, c));
116
+}
117
+
118
+static int write_test_ref(struct reftable_writer *wr, void *arg)
119
+{
120
+ struct reftable_ref_record *ref = arg;
121
+ reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
122
+ return reftable_writer_add_ref(wr, ref);
123
+}
124
+
125
+struct write_log_arg {
126
+ struct reftable_log_record *log;
127
+ uint64_t update_index;
128
+};
129
+
130
+static int write_test_log(struct reftable_writer *wr, void *arg)
131
+{
132
+ struct write_log_arg *wla = arg;
133
+
134
+ reftable_writer_set_limits(wr, wla->update_index, wla->update_index);
135
+ return reftable_writer_add_log(wr, wla->log);
136
+}
137
+
138
+static void test_reftable_stack_add_one(void)
139
+{
140
+ char *dir = get_tmp_dir(__LINE__);
141
+
142
+ struct reftable_write_options cfg = { 0 };
143
+ struct reftable_stack *st = NULL;
144
+ int err;
145
+ struct reftable_ref_record ref = {
146
+ .refname = "HEAD",
147
+ .update_index = 1,
148
+ .value_type = REFTABLE_REF_SYMREF,
149
+ .value.symref = "master",
150
+ };
151
+ struct reftable_ref_record dest = { NULL };
152
+
153
+
154
+ err = reftable_new_stack(&st, dir, cfg);
155
+ EXPECT_ERR(err);
156
+
157
+ err = reftable_stack_add(st, &write_test_ref, &ref);
158
+ EXPECT_ERR(err);
159
+
160
+ err = reftable_stack_read_ref(st, ref.refname, &dest);
161
+ EXPECT_ERR(err);
162
+ EXPECT(0 == strcmp("master", dest.value.symref));
163
+
164
+ printf("testing print functionality:\n");
165
+ err = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);
166
+ EXPECT_ERR(err);
167
+
168
+ err = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);
169
+ EXPECT(err == REFTABLE_FORMAT_ERROR);
170
+
171
+ reftable_ref_record_release(&dest);
172
+ reftable_stack_destroy(st);
173
+ clear_dir(dir);
174
+}
175
+
176
+static void test_reftable_stack_uptodate(void)
177
+{
178
+ struct reftable_write_options cfg = { 0 };
179
+ struct reftable_stack *st1 = NULL;
180
+ struct reftable_stack *st2 = NULL;
181
+ char *dir = get_tmp_dir(__LINE__);
182
+
183
+ int err;
184
+ struct reftable_ref_record ref1 = {
185
+ .refname = "HEAD",
186
+ .update_index = 1,
187
+ .value_type = REFTABLE_REF_SYMREF,
188
+ .value.symref = "master",
189
+ };
190
+ struct reftable_ref_record ref2 = {
191
+ .refname = "branch2",
192
+ .update_index = 2,
193
+ .value_type = REFTABLE_REF_SYMREF,
194
+ .value.symref = "master",
195
+ };
196
+
197
+
198
+ /* simulate multi-process access to the same stack
199
+ by creating two stacks for the same directory.
200
+ */
201
+ err = reftable_new_stack(&st1, dir, cfg);
202
+ EXPECT_ERR(err);
203
+
204
+ err = reftable_new_stack(&st2, dir, cfg);
205
+ EXPECT_ERR(err);
206
+
207
+ err = reftable_stack_add(st1, &write_test_ref, &ref1);
208
+ EXPECT_ERR(err);
209
+
210
+ err = reftable_stack_add(st2, &write_test_ref, &ref2);
211
+ EXPECT(err == REFTABLE_LOCK_ERROR);
212
+
213
+ err = reftable_stack_reload(st2);
214
+ EXPECT_ERR(err);
215
+
216
+ err = reftable_stack_add(st2, &write_test_ref, &ref2);
217
+ EXPECT_ERR(err);
218
+ reftable_stack_destroy(st1);
219
+ reftable_stack_destroy(st2);
220
+ clear_dir(dir);
221
+}
222
+
223
+static void test_reftable_stack_transaction_api(void)
224
+{
225
+ char *dir = get_tmp_dir(__LINE__);
226
+
227
+ struct reftable_write_options cfg = { 0 };
228
+ struct reftable_stack *st = NULL;
229
+ int err;
230
+ struct reftable_addition *add = NULL;
231
+
232
+ struct reftable_ref_record ref = {
233
+ .refname = "HEAD",
234
+ .update_index = 1,
235
+ .value_type = REFTABLE_REF_SYMREF,
236
+ .value.symref = "master",
237
+ };
238
+ struct reftable_ref_record dest = { NULL };
239
+
240
+
241
+ err = reftable_new_stack(&st, dir, cfg);
242
+ EXPECT_ERR(err);
243
+
244
+ reftable_addition_destroy(add);
245
+
246
+ err = reftable_stack_new_addition(&add, st);
247
+ EXPECT_ERR(err);
248
+
249
+ err = reftable_addition_add(add, &write_test_ref, &ref);
250
+ EXPECT_ERR(err);
251
+
252
+ err = reftable_addition_commit(add);
253
+ EXPECT_ERR(err);
254
+
255
+ reftable_addition_destroy(add);
256
+
257
+ err = reftable_stack_read_ref(st, ref.refname, &dest);
258
+ EXPECT_ERR(err);
259
+ EXPECT(REFTABLE_REF_SYMREF == dest.value_type);
260
+ EXPECT(0 == strcmp("master", dest.value.symref));
261
+
262
+ reftable_ref_record_release(&dest);
263
+ reftable_stack_destroy(st);
264
+ clear_dir(dir);
265
+}
266
+
267
+static void test_reftable_stack_validate_refname(void)
268
+{
269
+ struct reftable_write_options cfg = { 0 };
270
+ struct reftable_stack *st = NULL;
271
+ int err;
272
+ char *dir = get_tmp_dir(__LINE__);
273
+
274
+ int i;
275
+ struct reftable_ref_record ref = {
276
+ .refname = "a/b",
277
+ .update_index = 1,
278
+ .value_type = REFTABLE_REF_SYMREF,
279
+ .value.symref = "master",
280
+ };
281
+ char *additions[] = { "a", "a/b/c" };
282
+
283
+ err = reftable_new_stack(&st, dir, cfg);
284
+ EXPECT_ERR(err);
285
+
286
+ err = reftable_stack_add(st, &write_test_ref, &ref);
287
+ EXPECT_ERR(err);
288
+
289
+ for (i = 0; i < ARRAY_SIZE(additions); i++) {
290
+ struct reftable_ref_record ref = {
291
+ .refname = additions[i],
292
+ .update_index = 1,
293
+ .value_type = REFTABLE_REF_SYMREF,
294
+ .value.symref = "master",
295
+ };
296
+
297
+ err = reftable_stack_add(st, &write_test_ref, &ref);
298
+ EXPECT(err == REFTABLE_NAME_CONFLICT);
299
+ }
300
+
301
+ reftable_stack_destroy(st);
302
+ clear_dir(dir);
303
+}
304
+
305
+static int write_error(struct reftable_writer *wr, void *arg)
306
+{
307
+ return *((int *)arg);
308
+}
309
+
310
+static void test_reftable_stack_update_index_check(void)
311
+{
312
+ char *dir = get_tmp_dir(__LINE__);
313
+
314
+ struct reftable_write_options cfg = { 0 };
315
+ struct reftable_stack *st = NULL;
316
+ int err;
317
+ struct reftable_ref_record ref1 = {
318
+ .refname = "name1",
319
+ .update_index = 1,
320
+ .value_type = REFTABLE_REF_SYMREF,
321
+ .value.symref = "master",
322
+ };
323
+ struct reftable_ref_record ref2 = {
324
+ .refname = "name2",
325
+ .update_index = 1,
326
+ .value_type = REFTABLE_REF_SYMREF,
327
+ .value.symref = "master",
328
+ };
329
+
330
+ err = reftable_new_stack(&st, dir, cfg);
331
+ EXPECT_ERR(err);
332
+
333
+ err = reftable_stack_add(st, &write_test_ref, &ref1);
334
+ EXPECT_ERR(err);
335
+
336
+ err = reftable_stack_add(st, &write_test_ref, &ref2);
337
+ EXPECT(err == REFTABLE_API_ERROR);
338
+ reftable_stack_destroy(st);
339
+ clear_dir(dir);
340
+}
341
+
342
+static void test_reftable_stack_lock_failure(void)
343
+{
344
+ char *dir = get_tmp_dir(__LINE__);
345
+
346
+ struct reftable_write_options cfg = { 0 };
347
+ struct reftable_stack *st = NULL;
348
+ int err, i;
349
+
350
+ err = reftable_new_stack(&st, dir, cfg);
351
+ EXPECT_ERR(err);
352
+ for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
353
+ err = reftable_stack_add(st, &write_error, &i);
354
+ EXPECT(err == i);
355
+ }
356
+
357
+ reftable_stack_destroy(st);
358
+ clear_dir(dir);
359
+}
360
+
361
+static void test_reftable_stack_add(void)
362
+{
363
+ int i = 0;
364
+ int err = 0;
365
+ struct reftable_write_options cfg = {
366
+ .exact_log_message = 1,
367
+ };
368
+ struct reftable_stack *st = NULL;
369
+ char *dir = get_tmp_dir(__LINE__);
370
+
371
+ struct reftable_ref_record refs[2] = { { NULL } };
372
+ struct reftable_log_record logs[2] = { { NULL } };
373
+ int N = ARRAY_SIZE(refs);
374
+
375
+
376
+ err = reftable_new_stack(&st, dir, cfg);
377
+ EXPECT_ERR(err);
378
+ st->disable_auto_compact = 1;
379
+
380
+ for (i = 0; i < N; i++) {
381
+ char buf[256];
382
+ snprintf(buf, sizeof(buf), "branch%02d", i);
383
+ refs[i].refname = xstrdup(buf);
384
+ refs[i].update_index = i + 1;
385
+ refs[i].value_type = REFTABLE_REF_VAL1;
386
+ refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
387
+ set_test_hash(refs[i].value.val1, i);
388
+
389
+ logs[i].refname = xstrdup(buf);
390
+ logs[i].update_index = N + i + 1;
391
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
392
+
393
+ logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);
394
+ logs[i].value.update.email = xstrdup("identity@invalid");
395
+ set_test_hash(logs[i].value.update.new_hash, i);
396
+ }
397
+
398
+ for (i = 0; i < N; i++) {
399
+ int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
400
+ EXPECT_ERR(err);
401
+ }
402
+
403
+ for (i = 0; i < N; i++) {
404
+ struct write_log_arg arg = {
405
+ .log = &logs[i],
406
+ .update_index = reftable_stack_next_update_index(st),
407
+ };
408
+ int err = reftable_stack_add(st, &write_test_log, &arg);
409
+ EXPECT_ERR(err);
410
+ }
411
+
412
+ err = reftable_stack_compact_all(st, NULL);
413
+ EXPECT_ERR(err);
414
+
415
+ for (i = 0; i < N; i++) {
416
+ struct reftable_ref_record dest = { NULL };
417
+
418
+ int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
419
+ EXPECT_ERR(err);
420
+ EXPECT(reftable_ref_record_equal(&dest, refs + i,
421
+ GIT_SHA1_RAWSZ));
422
+ reftable_ref_record_release(&dest);
423
+ }
424
+
425
+ for (i = 0; i < N; i++) {
426
+ struct reftable_log_record dest = { NULL };
427
+ int err = reftable_stack_read_log(st, refs[i].refname, &dest);
428
+ EXPECT_ERR(err);
429
+ EXPECT(reftable_log_record_equal(&dest, logs + i,
430
+ GIT_SHA1_RAWSZ));
431
+ reftable_log_record_release(&dest);
432
+ }
433
+
434
+ /* cleanup */
435
+ reftable_stack_destroy(st);
436
+ for (i = 0; i < N; i++) {
437
+ reftable_ref_record_release(&refs[i]);
438
+ reftable_log_record_release(&logs[i]);
439
+ }
440
+ clear_dir(dir);
441
+}
442
+
443
+static void test_reftable_stack_log_normalize(void)
444
+{
445
+ int err = 0;
446
+ struct reftable_write_options cfg = {
447
+ 0,
448
+ };
449
+ struct reftable_stack *st = NULL;
450
+ char *dir = get_tmp_dir(__LINE__);
451
+
452
+ uint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };
453
+
454
+ struct reftable_log_record input = { .refname = "branch",
455
+ .update_index = 1,
456
+ .value_type = REFTABLE_LOG_UPDATE,
457
+ .value = { .update = {
458
+ .new_hash = h1,
459
+ .old_hash = h2,
460
+ } } };
461
+ struct reftable_log_record dest = {
462
+ .update_index = 0,
463
+ };
464
+ struct write_log_arg arg = {
465
+ .log = &input,
466
+ .update_index = 1,
467
+ };
468
+
469
+ err = reftable_new_stack(&st, dir, cfg);
470
+ EXPECT_ERR(err);
471
+
472
+ input.value.update.message = "one\ntwo";
473
+ err = reftable_stack_add(st, &write_test_log, &arg);
474
+ EXPECT(err == REFTABLE_API_ERROR);
475
+
476
+ input.value.update.message = "one";
477
+ err = reftable_stack_add(st, &write_test_log, &arg);
478
+ EXPECT_ERR(err);
479
+
480
+ err = reftable_stack_read_log(st, input.refname, &dest);
481
+ EXPECT_ERR(err);
482
+ EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
483
+
484
+ input.value.update.message = "two\n";
485
+ arg.update_index = 2;
486
+ err = reftable_stack_add(st, &write_test_log, &arg);
487
+ EXPECT_ERR(err);
488
+ err = reftable_stack_read_log(st, input.refname, &dest);
489
+ EXPECT_ERR(err);
490
+ EXPECT(0 == strcmp(dest.value.update.message, "two\n"));
491
+
492
+ /* cleanup */
493
+ reftable_stack_destroy(st);
494
+ reftable_log_record_release(&dest);
495
+ clear_dir(dir);
496
+}
497
+
498
+static void test_reftable_stack_tombstone(void)
499
+{
500
+ int i = 0;
501
+ char *dir = get_tmp_dir(__LINE__);
502
+
503
+ struct reftable_write_options cfg = { 0 };
504
+ struct reftable_stack *st = NULL;
505
+ int err;
506
+ struct reftable_ref_record refs[2] = { { NULL } };
507
+ struct reftable_log_record logs[2] = { { NULL } };
508
+ int N = ARRAY_SIZE(refs);
509
+ struct reftable_ref_record dest = { NULL };
510
+ struct reftable_log_record log_dest = { NULL };
511
+
512
+
513
+ err = reftable_new_stack(&st, dir, cfg);
514
+ EXPECT_ERR(err);
515
+
516
+ /* even entries add the refs, odd entries delete them. */
517
+ for (i = 0; i < N; i++) {
518
+ const char *buf = "branch";
519
+ refs[i].refname = xstrdup(buf);
520
+ refs[i].update_index = i + 1;
521
+ if (i % 2 == 0) {
522
+ refs[i].value_type = REFTABLE_REF_VAL1;
523
+ refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
524
+ set_test_hash(refs[i].value.val1, i);
525
+ }
526
+
527
+ logs[i].refname = xstrdup(buf);
528
+ /* update_index is part of the key. */
529
+ logs[i].update_index = 42;
530
+ if (i % 2 == 0) {
531
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
532
+ logs[i].value.update.new_hash =
533
+ reftable_malloc(GIT_SHA1_RAWSZ);
534
+ set_test_hash(logs[i].value.update.new_hash, i);
535
+ logs[i].value.update.email =
536
+ xstrdup("identity@invalid");
537
+ }
538
+ }
539
+ for (i = 0; i < N; i++) {
540
+ int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
541
+ EXPECT_ERR(err);
542
+ }
543
+
544
+ for (i = 0; i < N; i++) {
545
+ struct write_log_arg arg = {
546
+ .log = &logs[i],
547
+ .update_index = reftable_stack_next_update_index(st),
548
+ };
549
+ int err = reftable_stack_add(st, &write_test_log, &arg);
550
+ EXPECT_ERR(err);
551
+ }
552
+
553
+ err = reftable_stack_read_ref(st, "branch", &dest);
554
+ EXPECT(err == 1);
555
+ reftable_ref_record_release(&dest);
556
+
557
+ err = reftable_stack_read_log(st, "branch", &log_dest);
558
+ EXPECT(err == 1);
559
+ reftable_log_record_release(&log_dest);
560
+
561
+ err = reftable_stack_compact_all(st, NULL);
562
+ EXPECT_ERR(err);
563
+
564
+ err = reftable_stack_read_ref(st, "branch", &dest);
565
+ EXPECT(err == 1);
566
+
567
+ err = reftable_stack_read_log(st, "branch", &log_dest);
568
+ EXPECT(err == 1);
569
+ reftable_ref_record_release(&dest);
570
+ reftable_log_record_release(&log_dest);
571
+
572
+ /* cleanup */
573
+ reftable_stack_destroy(st);
574
+ for (i = 0; i < N; i++) {
575
+ reftable_ref_record_release(&refs[i]);
576
+ reftable_log_record_release(&logs[i]);
577
+ }
578
+ clear_dir(dir);
579
+}
580
+
581
+static void test_reftable_stack_hash_id(void)
582
+{
583
+ char *dir = get_tmp_dir(__LINE__);
584
+
585
+ struct reftable_write_options cfg = { 0 };
586
+ struct reftable_stack *st = NULL;
587
+ int err;
588
+
589
+ struct reftable_ref_record ref = {
590
+ .refname = "master",
591
+ .value_type = REFTABLE_REF_SYMREF,
592
+ .value.symref = "target",
593
+ .update_index = 1,
594
+ };
595
+ struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
596
+ struct reftable_stack *st32 = NULL;
597
+ struct reftable_write_options cfg_default = { 0 };
598
+ struct reftable_stack *st_default = NULL;
599
+ struct reftable_ref_record dest = { NULL };
600
+
601
+ err = reftable_new_stack(&st, dir, cfg);
602
+ EXPECT_ERR(err);
603
+
604
+ err = reftable_stack_add(st, &write_test_ref, &ref);
605
+ EXPECT_ERR(err);
606
+
607
+ /* can't read it with the wrong hash ID. */
608
+ err = reftable_new_stack(&st32, dir, cfg32);
609
+ EXPECT(err == REFTABLE_FORMAT_ERROR);
610
+
611
+ /* check that we can read it back with default config too. */
612
+ err = reftable_new_stack(&st_default, dir, cfg_default);
613
+ EXPECT_ERR(err);
614
+
615
+ err = reftable_stack_read_ref(st_default, "master", &dest);
616
+ EXPECT_ERR(err);
617
+
618
+ EXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
619
+ reftable_ref_record_release(&dest);
620
+ reftable_stack_destroy(st);
621
+ reftable_stack_destroy(st_default);
622
+ clear_dir(dir);
623
+}
624
+
625
+static void test_log2(void)
626
+{
627
+ EXPECT(1 == fastlog2(3));
628
+ EXPECT(2 == fastlog2(4));
629
+ EXPECT(2 == fastlog2(5));
630
+}
631
+
632
+static void test_sizes_to_segments(void)
633
+{
634
+ uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };
635
+ /* .................0 1 2 3 4 5 */
636
+
637
+ int seglen = 0;
638
+ struct segment *segs =
639
+ sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
640
+ EXPECT(segs[2].log == 3);
641
+ EXPECT(segs[2].start == 5);
642
+ EXPECT(segs[2].end == 6);
643
+
644
+ EXPECT(segs[1].log == 2);
645
+ EXPECT(segs[1].start == 2);
646
+ EXPECT(segs[1].end == 5);
647
+ reftable_free(segs);
648
+}
649
+
650
+static void test_sizes_to_segments_empty(void)
651
+{
652
+ int seglen = 0;
653
+ struct segment *segs = sizes_to_segments(&seglen, NULL, 0);
654
+ EXPECT(seglen == 0);
655
+ reftable_free(segs);
656
+}
657
+
658
+static void test_sizes_to_segments_all_equal(void)
659
+{
660
+ uint64_t sizes[] = { 5, 5 };
661
+
662
+ int seglen = 0;
663
+ struct segment *segs =
664
+ sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
665
+ EXPECT(seglen == 1);
666
+ EXPECT(segs[0].start == 0);
667
+ EXPECT(segs[0].end == 2);
668
+ reftable_free(segs);
669
+}
670
+
671
+static void test_suggest_compaction_segment(void)
672
+{
673
+ uint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };
674
+ /* .................0 1 2 3 4 5 6 */
675
+ struct segment min =
676
+ suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
677
+ EXPECT(min.start == 2);
678
+ EXPECT(min.end == 7);
679
+}
680
+
681
+static void test_suggest_compaction_segment_nothing(void)
682
+{
683
+ uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
684
+ struct segment result =
685
+ suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
686
+ EXPECT(result.start == result.end);
687
+}
688
+
689
+static void test_reflog_expire(void)
690
+{
691
+ char *dir = get_tmp_dir(__LINE__);
692
+
693
+ struct reftable_write_options cfg = { 0 };
694
+ struct reftable_stack *st = NULL;
695
+ struct reftable_log_record logs[20] = { { NULL } };
696
+ int N = ARRAY_SIZE(logs) - 1;
697
+ int i = 0;
698
+ int err;
699
+ struct reftable_log_expiry_config expiry = {
700
+ .time = 10,
701
+ };
702
+ struct reftable_log_record log = { NULL };
703
+
704
+
705
+ err = reftable_new_stack(&st, dir, cfg);
706
+ EXPECT_ERR(err);
707
+
708
+ for (i = 1; i <= N; i++) {
709
+ char buf[256];
710
+ snprintf(buf, sizeof(buf), "branch%02d", i);
711
+
712
+ logs[i].refname = xstrdup(buf);
713
+ logs[i].update_index = i;
714
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
715
+ logs[i].value.update.time = i;
716
+ logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);
717
+ logs[i].value.update.email = xstrdup("identity@invalid");
718
+ set_test_hash(logs[i].value.update.new_hash, i);
719
+ }
720
+
721
+ for (i = 1; i <= N; i++) {
722
+ struct write_log_arg arg = {
723
+ .log = &logs[i],
724
+ .update_index = reftable_stack_next_update_index(st),
725
+ };
726
+ int err = reftable_stack_add(st, &write_test_log, &arg);
727
+ EXPECT_ERR(err);
728
+ }
729
+
730
+ err = reftable_stack_compact_all(st, NULL);
731
+ EXPECT_ERR(err);
732
+
733
+ err = reftable_stack_compact_all(st, &expiry);
734
+ EXPECT_ERR(err);
735
+
736
+ err = reftable_stack_read_log(st, logs[9].refname, &log);
737
+ EXPECT(err == 1);
738
+
739
+ err = reftable_stack_read_log(st, logs[11].refname, &log);
740
+ EXPECT_ERR(err);
741
+
742
+ expiry.min_update_index = 15;
743
+ err = reftable_stack_compact_all(st, &expiry);
744
+ EXPECT_ERR(err);
745
+
746
+ err = reftable_stack_read_log(st, logs[14].refname, &log);
747
+ EXPECT(err == 1);
748
+
749
+ err = reftable_stack_read_log(st, logs[16].refname, &log);
750
+ EXPECT_ERR(err);
751
+
752
+ /* cleanup */
753
+ reftable_stack_destroy(st);
754
+ for (i = 0; i <= N; i++) {
755
+ reftable_log_record_release(&logs[i]);
756
+ }
757
+ clear_dir(dir);
758
+ reftable_log_record_release(&log);
759
+}
760
+
761
+static int write_nothing(struct reftable_writer *wr, void *arg)
762
+{
763
+ reftable_writer_set_limits(wr, 1, 1);
764
+ return 0;
765
+}
766
+
767
+static void test_empty_add(void)
768
+{
769
+ struct reftable_write_options cfg = { 0 };
770
+ struct reftable_stack *st = NULL;
771
+ int err;
772
+ char *dir = get_tmp_dir(__LINE__);
773
+
774
+ struct reftable_stack *st2 = NULL;
775
+
776
+
777
+ err = reftable_new_stack(&st, dir, cfg);
778
+ EXPECT_ERR(err);
779
+
780
+ err = reftable_stack_add(st, &write_nothing, NULL);
781
+ EXPECT_ERR(err);
782
+
783
+ err = reftable_new_stack(&st2, dir, cfg);
784
+ EXPECT_ERR(err);
785
+ clear_dir(dir);
786
+ reftable_stack_destroy(st);
787
+ reftable_stack_destroy(st2);
788
+}
789
+
790
+static void test_reftable_stack_auto_compaction(void)
791
+{
792
+ struct reftable_write_options cfg = { 0 };
793
+ struct reftable_stack *st = NULL;
794
+ char *dir = get_tmp_dir(__LINE__);
795
+
796
+ int err, i;
797
+ int N = 100;
798
+
799
+ err = reftable_new_stack(&st, dir, cfg);
800
+ EXPECT_ERR(err);
801
+
802
+ st->disable_auto_compact = 1; /* call manually below for coverage. */
803
+ for (i = 0; i < N; i++) {
804
+ char name[100];
805
+ struct reftable_ref_record ref = {
806
+ .refname = name,
807
+ .update_index = reftable_stack_next_update_index(st),
808
+ .value_type = REFTABLE_REF_SYMREF,
809
+ .value.symref = "master",
810
+ };
811
+ snprintf(name, sizeof(name), "branch%04d", i);
812
+
813
+ err = reftable_stack_add(st, &write_test_ref, &ref);
814
+ EXPECT_ERR(err);
815
+
816
+ err = reftable_stack_auto_compact(st);
817
+ EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
818
+ }
819
+
820
+ EXPECT(reftable_stack_compaction_stats(st)->entries_written <
821
+ (uint64_t)(N * fastlog2(N)));
822
+
823
+ reftable_stack_destroy(st);
824
+ clear_dir(dir);
825
+}
826
+
827
+static void test_reftable_stack_compaction_concurrent(void)
828
+{
829
+ struct reftable_write_options cfg = { 0 };
830
+ struct reftable_stack *st1 = NULL, *st2 = NULL;
831
+ char *dir = get_tmp_dir(__LINE__);
832
+
833
+ int err, i;
834
+ int N = 3;
835
+
836
+ err = reftable_new_stack(&st1, dir, cfg);
837
+ EXPECT_ERR(err);
838
+
839
+ for (i = 0; i < N; i++) {
840
+ char name[100];
841
+ struct reftable_ref_record ref = {
842
+ .refname = name,
843
+ .update_index = reftable_stack_next_update_index(st1),
844
+ .value_type = REFTABLE_REF_SYMREF,
845
+ .value.symref = "master",
846
+ };
847
+ snprintf(name, sizeof(name), "branch%04d", i);
848
+
849
+ err = reftable_stack_add(st1, &write_test_ref, &ref);
850
+ EXPECT_ERR(err);
851
+ }
852
+
853
+ err = reftable_new_stack(&st2, dir, cfg);
854
+ EXPECT_ERR(err);
855
+
856
+ err = reftable_stack_compact_all(st1, NULL);
857
+ EXPECT_ERR(err);
858
+
859
+ reftable_stack_destroy(st1);
860
+ reftable_stack_destroy(st2);
861
+
862
+ EXPECT(count_dir_entries(dir) == 2);
863
+ clear_dir(dir);
864
+}
865
+
866
+static void unclean_stack_close(struct reftable_stack *st)
867
+{
868
+ /* break abstraction boundary to simulate unclean shutdown. */
869
+ int i = 0;
870
+ for (; i < st->readers_len; i++) {
871
+ reftable_reader_free(st->readers[i]);
872
+ }
873
+ st->readers_len = 0;
874
+ FREE_AND_NULL(st->readers);
875
+}
876
+
877
+static void test_reftable_stack_compaction_concurrent_clean(void)
878
+{
879
+ struct reftable_write_options cfg = { 0 };
880
+ struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
881
+ char *dir = get_tmp_dir(__LINE__);
882
+
883
+ int err, i;
884
+ int N = 3;
885
+
886
+ err = reftable_new_stack(&st1, dir, cfg);
887
+ EXPECT_ERR(err);
888
+
889
+ for (i = 0; i < N; i++) {
890
+ char name[100];
891
+ struct reftable_ref_record ref = {
892
+ .refname = name,
893
+ .update_index = reftable_stack_next_update_index(st1),
894
+ .value_type = REFTABLE_REF_SYMREF,
895
+ .value.symref = "master",
896
+ };
897
+ snprintf(name, sizeof(name), "branch%04d", i);
898
+
899
+ err = reftable_stack_add(st1, &write_test_ref, &ref);
900
+ EXPECT_ERR(err);
901
+ }
902
+
903
+ err = reftable_new_stack(&st2, dir, cfg);
904
+ EXPECT_ERR(err);
905
+
906
+ err = reftable_stack_compact_all(st1, NULL);
907
+ EXPECT_ERR(err);
908
+
909
+ unclean_stack_close(st1);
910
+ unclean_stack_close(st2);
911
+
912
+ err = reftable_new_stack(&st3, dir, cfg);
913
+ EXPECT_ERR(err);
914
+
915
+ err = reftable_stack_clean(st3);
916
+ EXPECT_ERR(err);
917
+ EXPECT(count_dir_entries(dir) == 2);
918
+
919
+ reftable_stack_destroy(st1);
920
+ reftable_stack_destroy(st2);
921
+ reftable_stack_destroy(st3);
922
+
923
+ clear_dir(dir);
924
+}
925
+
926
+int stack_test_main(int argc, const char *argv[])
927
+{
928
+ RUN_TEST(test_empty_add);
929
+ RUN_TEST(test_log2);
930
+ RUN_TEST(test_names_equal);
931
+ RUN_TEST(test_parse_names);
932
+ RUN_TEST(test_read_file);
933
+ RUN_TEST(test_reflog_expire);
934
+ RUN_TEST(test_reftable_stack_add);
935
+ RUN_TEST(test_reftable_stack_add_one);
936
+ RUN_TEST(test_reftable_stack_auto_compaction);
937
+ RUN_TEST(test_reftable_stack_compaction_concurrent);
938
+ RUN_TEST(test_reftable_stack_compaction_concurrent_clean);
939
+ RUN_TEST(test_reftable_stack_hash_id);
940
+ RUN_TEST(test_reftable_stack_lock_failure);
941
+ RUN_TEST(test_reftable_stack_log_normalize);
942
+ RUN_TEST(test_reftable_stack_tombstone);
943
+ RUN_TEST(test_reftable_stack_transaction_api);
944
+ RUN_TEST(test_reftable_stack_update_index_check);
945
+ RUN_TEST(test_reftable_stack_uptodate);
946
+ RUN_TEST(test_reftable_stack_validate_refname);
947
+ RUN_TEST(test_sizes_to_segments);
948
+ RUN_TEST(test_sizes_to_segments_all_equal);
949
+ RUN_TEST(test_sizes_to_segments_empty);
950
+ RUN_TEST(test_suggest_compaction_segment);
951
+ RUN_TEST(test_suggest_compaction_segment_nothing);
952
+ return 0;
953
+}
t/helper/test-reftable.c
+1
@@ -10,6 +10,7 @@ int cmd__reftable(int argc, const char **argv)
10
record_test_main(argc, argv);
11
refname_test_main(argc, argv);
12
readwrite_test_main(argc, argv);
13
+ stack_test_main(argc, argv);
14
tree_test_main(argc, argv);
15
return 0;
16
}