Notes API: for_each_note(): Traverse the entire notes tree with a callback
This includes a first attempt at creating an optimal fanout scheme (which is calculated on-the-fly, while traversing). Signed-off-by: Johan Herland <johan@herland.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Johan Herland committed
Feb 13, 2010 at 22:28 UTC
73f77b909f87fcaece42ec50d8d0c1c35efbf947
2 files changed
+180
notes.c
+133
@@ -413,6 +413,133 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,
413
free(buf);
414
}
415
416
+/*
417
+ * Determine optimal on-disk fanout for this part of the notes tree
418
+ *
419
+ * Given a (sub)tree and the level in the internal tree structure, determine
420
+ * whether or not the given existing fanout should be expanded for this
421
+ * (sub)tree.
422
+ *
423
+ * Values of the 'fanout' variable:
424
+ * - 0: No fanout (all notes are stored directly in the root notes tree)
425
+ * - 1: 2/38 fanout
426
+ * - 2: 2/2/36 fanout
427
+ * - 3: 2/2/2/34 fanout
428
+ * etc.
429
+ */
430
+static unsigned char determine_fanout(struct int_node *tree, unsigned char n,
431
+ unsigned char fanout)
432
+{
433
+ /*
434
+ * The following is a simple heuristic that works well in practice:
435
+ * For each even-numbered 16-tree level (remember that each on-disk
436
+ * fanout level corresponds to _two_ 16-tree levels), peek at all 16
437
+ * entries at that tree level. If all of them are either int_nodes or
438
+ * subtree entries, then there are likely plenty of notes below this
439
+ * level, so we return an incremented fanout.
440
+ */
441
+ unsigned int i;
442
+ if ((n % 2) || (n > 2 * fanout))
443
+ return fanout;
444
+ for (i = 0; i < 16; i++) {
445
+ switch (GET_PTR_TYPE(tree->a[i])) {
446
+ case PTR_TYPE_SUBTREE:
447
+ case PTR_TYPE_INTERNAL:
448
+ continue;
449
+ default:
450
+ return fanout;
451
+ }
452
+ }
453
+ return fanout + 1;
454
+}
455
+
456
+static void construct_path_with_fanout(const unsigned char *sha1,
457
+ unsigned char fanout, char *path)
458
+{
459
+ unsigned int i = 0, j = 0;
460
+ const char *hex_sha1 = sha1_to_hex(sha1);
461
+ assert(fanout < 20);
462
+ while (fanout) {
463
+ path[i++] = hex_sha1[j++];
464
+ path[i++] = hex_sha1[j++];
465
+ path[i++] = '/';
466
+ fanout--;
467
+ }
468
+ strcpy(path + i, hex_sha1 + j);
469
+}
470
+
471
+static int for_each_note_helper(struct int_node *tree, unsigned char n,
472
+ unsigned char fanout, int flags, each_note_fn fn,
473
+ void *cb_data)
474
+{
475
+ unsigned int i;
476
+ void *p;
477
+ int ret = 0;
478
+ struct leaf_node *l;
479
+ static char path[40 + 19 + 1]; /* hex SHA1 + 19 * '/' + NUL */
480
+
481
+ fanout = determine_fanout(tree, n, fanout);
482
+ for (i = 0; i < 16; i++) {
483
+redo:
484
+ p = tree->a[i];
485
+ switch (GET_PTR_TYPE(p)) {
486
+ case PTR_TYPE_INTERNAL:
487
+ /* recurse into int_node */
488
+ ret = for_each_note_helper(CLR_PTR_TYPE(p), n + 1,
489
+ fanout, flags, fn, cb_data);
490
+ break;
491
+ case PTR_TYPE_SUBTREE:
492
+ l = (struct leaf_node *) CLR_PTR_TYPE(p);
493
+ /*
494
+ * Subtree entries in the note tree represent parts of
495
+ * the note tree that have not yet been explored. There
496
+ * is a direct relationship between subtree entries at
497
+ * level 'n' in the tree, and the 'fanout' variable:
498
+ * Subtree entries at level 'n <= 2 * fanout' should be
499
+ * preserved, since they correspond exactly to a fanout
500
+ * directory in the on-disk structure. However, subtree
501
+ * entries at level 'n > 2 * fanout' should NOT be
502
+ * preserved, but rather consolidated into the above
503
+ * notes tree level. We achieve this by unconditionally
504
+ * unpacking subtree entries that exist below the
505
+ * threshold level at 'n = 2 * fanout'.
506
+ */
507
+ if (n <= 2 * fanout &&
508
+ flags & FOR_EACH_NOTE_YIELD_SUBTREES) {
509
+ /* invoke callback with subtree */
510
+ unsigned int path_len =
511
+ l->key_sha1[19] * 2 + fanout;
512
+ assert(path_len < 40 + 19);
513
+ construct_path_with_fanout(l->key_sha1, fanout,
514
+ path);
515
+ /* Create trailing slash, if needed */
516
+ if (path[path_len - 1] != '/')
517
+ path[path_len++] = '/';
518
+ path[path_len] = '\0';
519
+ ret = fn(l->key_sha1, l->val_sha1, path,
520
+ cb_data);
521
+ }
522
+ if (n > fanout * 2 ||
523
+ !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) {
524
+ /* unpack subtree and resume traversal */
525
+ tree->a[i] = NULL;
526
+ load_subtree(l, tree, n);
527
+ free(l);
528
+ goto redo;
529
+ }
530
+ break;
531
+ case PTR_TYPE_NOTE:
532
+ l = (struct leaf_node *) CLR_PTR_TYPE(p);
533
+ construct_path_with_fanout(l->key_sha1, fanout, path);
534
+ ret = fn(l->key_sha1, l->val_sha1, path, cb_data);
535
+ break;
536
+ }
537
+ if (ret)
538
+ return ret;
539
+ }
540
+ return 0;
541
+}
542
+
543
void init_notes(const char *notes_ref, int flags)
544
{
545
unsigned char sha1[20], object_sha1[20];
@@ -471,6 +598,12 @@ const unsigned char *get_note(const unsigned char *object_sha1)
598
return found ? found->val_sha1 : NULL;
599
}
600
601
+int for_each_note(int flags, each_note_fn fn, void *cb_data)
602
+{
603
+ assert(initialized);
604
+ return for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data);
605
+}
606
+
607
void free_notes(void)
608
{
609
note_tree_free(&root_node);
notes.h
+47
@@ -35,6 +35,53 @@ void remove_note(const unsigned char *object_sha1);
35
*/
36
const unsigned char *get_note(const unsigned char *object_sha1);
37
38
+/*
39
+ * Flags controlling behaviour of for_each_note()
40
+ *
41
+ * Default behaviour of for_each_note() is to traverse every single note object
42
+ * in the notes tree, unpacking subtree entries along the way.
43
+ * The following flags can be used to alter the default behaviour:
44
+ *
45
+ * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into
46
+ * subtree entries while traversing the notes tree. This causes notes within
47
+ * those subtrees NOT to be passed to the callback. Use this flag if you
48
+ * don't want to traverse _all_ notes, but only want to traverse the parts
49
+ * of the notes tree that have already been unpacked (this includes at least
50
+ * all notes that have been added/changed).
51
+ *
52
+ * - YIELD_SUBTREES causes any subtree entries that are encountered to be
53
+ * passed to the callback, before recursing into them. Subtree entries are
54
+ * not note objects, but represent intermediate directories in the notes
55
+ * tree. When passed to the callback, subtree entries will have a trailing
56
+ * slash in their path, which the callback may use to differentiate between
57
+ * note entries and subtree entries. Note that already-unpacked subtree
58
+ * entries are not part of the notes tree, and will therefore not be yielded.
59
+ * If this flag is used together with DONT_UNPACK_SUBTREES, for_each_note()
60
+ * will yield the subtree entry, but not recurse into it.
61
+ */
62
+#define FOR_EACH_NOTE_DONT_UNPACK_SUBTREES 1
63
+#define FOR_EACH_NOTE_YIELD_SUBTREES 2
64
+
65
+/*
66
+ * Invoke the specified callback function for each note
67
+ *
68
+ * If the callback returns nonzero, the note walk is aborted, and the return
69
+ * value from the callback is returned from for_each_note(). Hence, a zero
70
+ * return value from for_each_note() indicates that all notes were walked
71
+ * successfully.
72
+ *
73
+ * IMPORTANT: The callback function is NOT allowed to change the notes tree.
74
+ * In other words, the following functions can NOT be invoked (on the current
75
+ * notes tree) from within the callback:
76
+ * - add_note()
77
+ * - remove_note()
78
+ * - free_notes()
79
+ */
80
+typedef int each_note_fn(const unsigned char *object_sha1,
81
+ const unsigned char *note_sha1, char *note_path,
82
+ void *cb_data);
83
+int for_each_note(int flags, each_note_fn fn, void *cb_data);
84
+
85
/* Free (and de-initialize) the internal notes tree structure */
86
void free_notes(void);
87