/* * To remove a leaf_node: * Search to the tree location appropriate for the given leaf_node's key: * - If location does not hold a matching entry, abort and do nothing. * - Replace the matching leaf_node with a NULL entry (and free the leaf_node). * - Consolidate int_nodes repeatedly, while walking up the tree towards root. */ static void note_tree_remove(struct notes_tree *t, struct int_node *tree, unsigned char n, struct leaf_node *entry) { struct leaf_node *l; struct int_node *parent_stack[20]; unsigned char i, j; void **p = note_tree_search(t, &tree, &n, entry->key_sha1); assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE) return; /* type mismatch, nothing to remove */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); if (hashcmp(l->key_sha1, entry->key_sha1)) return; /* key mismatch, nothing to remove */ /* we have found a matching entry */ free(l); *p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL); /* consolidate this tree level, and parent levels, if possible */ if (!n) return; /* cannot consolidate top level */ /* first, build stack of ancestors between root and current node */ parent_stack[0] = t->root; for (i = 0; i < n; i++) { j = GET_NIBBLE(i, entry->key_sha1); parent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]); } assert(i == n && parent_stack[i] == tree); /* next, unwind stack until note_tree_consolidate() is done */ while (i > 0 && !note_tree_consolidate(parent_stack[i], parent_stack[i - 1], GET_NIBBLE(i - 1, entry->key_sha1))) i--; }
/* * Determine optimal on-disk fanout for this part of the notes tree * * Given a (sub)tree and the level in the internal tree structure, determine * whether or not the given existing fanout should be expanded for this * (sub)tree. * * Values of the 'fanout' variable: * - 0: No fanout (all notes are stored directly in the root notes tree) * - 1: 2/38 fanout * - 2: 2/2/36 fanout * - 3: 2/2/2/34 fanout * etc. */ static unsigned char determine_fanout(struct int_node *tree, unsigned char n, unsigned char fanout) { /* * The following is a simple heuristic that works well in practice: * For each even-numbered 16-tree level (remember that each on-disk * fanout level corresponds to _two_ 16-tree levels), peek at all 16 * entries at that tree level. If all of them are either int_nodes or * subtree entries, then there are likely plenty of notes below this * level, so we return an incremented fanout. */ unsigned int i; if ((n % 2) || (n > 2 * fanout)) return fanout; for (i = 0; i < 16; i++) { switch (GET_PTR_TYPE(tree->a[i])) { case PTR_TYPE_SUBTREE: case PTR_TYPE_INTERNAL: continue; default: return fanout; } } return fanout + 1; }
/* * To find a leaf_node: * Search to the tree location appropriate for the given key: * If a note entry with matching key, return the note entry, else return NULL. */ static struct leaf_node *note_tree_find(struct notes_tree *t, struct int_node *tree, unsigned char n, const unsigned char *key_sha1) { void **p = note_tree_search(t, &tree, &n, key_sha1); if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); if (hasheq(key_sha1, l->key_oid.hash)) return l; } return NULL; }
/* Free the entire notes data contained in the given tree */ static void note_tree_free(struct int_node *tree) { unsigned int i; for (i = 0; i < 16; i++) { void *p = tree->a[i]; switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: note_tree_free(CLR_PTR_TYPE(p)); /* fall through */ case PTR_TYPE_NOTE: case PTR_TYPE_SUBTREE: free(CLR_PTR_TYPE(p)); } } }
/* * How to consolidate an int_node: * If there are > 1 non-NULL entries, give up and return non-zero. * Otherwise replace the int_node at the given index in the given parent node * with the only NOTE entry (or a NULL entry if no entries) from the given * tree, and return 0. */ static int note_tree_consolidate(struct int_node *tree, struct int_node *parent, unsigned char index) { unsigned int i; void *p = NULL; assert(tree && parent); assert(CLR_PTR_TYPE(parent->a[index]) == tree); for (i = 0; i < 16; i++) { if (GET_PTR_TYPE(tree->a[i]) != PTR_TYPE_NULL) { if (p) /* more than one entry */ return -2; p = tree->a[i]; } } if (p && (GET_PTR_TYPE(p) != PTR_TYPE_NOTE)) return -2; /* replace tree with p in parent[index] */ parent->a[index] = p; free(tree); return 0; }
static int for_each_note_helper(struct notes_tree *t, struct int_node *tree, unsigned char n, unsigned char fanout, int flags, each_note_fn fn, void *cb_data) { unsigned int i; void *p; int ret = 0; struct leaf_node *l; static char path[FANOUT_PATH_MAX]; fanout = determine_fanout(tree, n, fanout); for (i = 0; i < 16; i++) { redo: p = tree->a[i]; switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: /* recurse into int_node */ ret = for_each_note_helper(t, CLR_PTR_TYPE(p), n + 1, fanout, flags, fn, cb_data); break; case PTR_TYPE_SUBTREE: l = (struct leaf_node *) CLR_PTR_TYPE(p); /* * Subtree entries in the note tree represent parts of * the note tree that have not yet been explored. There * is a direct relationship between subtree entries at * level 'n' in the tree, and the 'fanout' variable: * Subtree entries at level 'n <= 2 * fanout' should be * preserved, since they correspond exactly to a fanout * directory in the on-disk structure. However, subtree * entries at level 'n > 2 * fanout' should NOT be * preserved, but rather consolidated into the above * notes tree level. We achieve this by unconditionally * unpacking subtree entries that exist below the * threshold level at 'n = 2 * fanout'. */ if (n <= 2 * fanout && flags & FOR_EACH_NOTE_YIELD_SUBTREES) { /* invoke callback with subtree */ unsigned int path_len = l->key_oid.hash[KEY_INDEX] * 2 + fanout; assert(path_len < FANOUT_PATH_MAX - 1); construct_path_with_fanout(l->key_oid.hash, fanout, path); /* Create trailing slash, if needed */ if (path[path_len - 1] != '/') path[path_len++] = '/'; path[path_len] = '\0'; ret = fn(&l->key_oid, &l->val_oid, path, cb_data); } if (n > fanout * 2 || !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) { /* unpack subtree and resume traversal */ tree->a[i] = NULL; load_subtree(t, l, tree, n); free(l); goto redo; } break; case PTR_TYPE_NOTE: l = (struct leaf_node *) CLR_PTR_TYPE(p); construct_path_with_fanout(l->key_oid.hash, fanout, path); ret = fn(&l->key_oid, &l->val_oid, path, cb_data); break; } if (ret) return ret; } return 0; }
/* * To insert a leaf_node: * Search to the tree location appropriate for the given leaf_node's key: * - If location is unused (NULL), store the tweaked pointer directly there * - If location holds a note entry that matches the note-to-be-inserted, then * combine the two notes (by calling the given combine_notes function). * - If location holds a note entry that matches the subtree-to-be-inserted, * then unpack the subtree-to-be-inserted into the location. * - If location holds a matching subtree entry, unpack the subtree at that * location, and restart the insert operation from that level. * - Else, create a new int_node, holding both the node-at-location and the * node-to-be-inserted, and store the new int_node into the location. */ static int note_tree_insert(struct notes_tree *t, struct int_node *tree, unsigned char n, struct leaf_node *entry, unsigned char type, combine_notes_fn combine_notes) { struct int_node *new_node; struct leaf_node *l; void **p = note_tree_search(t, &tree, &n, entry->key_oid.hash); int ret = 0; assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); switch (GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: assert(!*p); if (is_null_oid(&entry->val_oid)) free(entry); else *p = SET_PTR_TYPE(entry, type); return 0; case PTR_TYPE_NOTE: switch (type) { case PTR_TYPE_NOTE: if (oideq(&l->key_oid, &entry->key_oid)) { /* skip concatenation if l == entry */ if (oideq(&l->val_oid, &entry->val_oid)) return 0; ret = combine_notes(&l->val_oid, &entry->val_oid); if (!ret && is_null_oid(&l->val_oid)) note_tree_remove(t, tree, n, entry); free(entry); return ret; } break; case PTR_TYPE_SUBTREE: if (!SUBTREE_SHA1_PREFIXCMP(l->key_oid.hash, entry->key_oid.hash)) { /* unpack 'entry' */ load_subtree(t, entry, tree, n); free(entry); return 0; } break; } break; case PTR_TYPE_SUBTREE: if (!SUBTREE_SHA1_PREFIXCMP(entry->key_oid.hash, l->key_oid.hash)) { /* unpack 'l' and restart insert */ *p = NULL; load_subtree(t, l, tree, n); free(l); return note_tree_insert(t, tree, n, entry, type, combine_notes); } break; } /* non-matching leaf_node */ assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); if (is_null_oid(&entry->val_oid)) { /* skip insertion of empty note */ free(entry); return 0; } new_node = (struct int_node *) xcalloc(1, sizeof(struct int_node)); ret = note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p), combine_notes); if (ret) return ret; *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); return note_tree_insert(t, new_node, n + 1, entry, type, combine_notes); }
/* * To insert a leaf_node: * Search to the tree location appropriate for the given leaf_node's key: * - If location is unused (NULL), store the tweaked pointer directly there * - If location holds a note entry that matches the note-to-be-inserted, then * combine the two notes (by calling the given combine_notes function). * - If location holds a note entry that matches the subtree-to-be-inserted, * then unpack the subtree-to-be-inserted into the location. * - If location holds a matching subtree entry, unpack the subtree at that * location, and restart the insert operation from that level. * - Else, create a new int_node, holding both the node-at-location and the * node-to-be-inserted, and store the new int_node into the location. */ static void note_tree_insert(struct notes_tree *t, struct int_node *tree, unsigned char n, struct leaf_node *entry, unsigned char type, combine_notes_fn combine_notes) { struct int_node *new_node; struct leaf_node *l; void **p = note_tree_search(t, &tree, &n, entry->key_sha1); assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); switch (GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: assert(!*p); *p = SET_PTR_TYPE(entry, type); return; case PTR_TYPE_NOTE: switch (type) { case PTR_TYPE_NOTE: if (!hashcmp(l->key_sha1, entry->key_sha1)) { /* skip concatenation if l == entry */ if (!hashcmp(l->val_sha1, entry->val_sha1)) return; if (combine_notes(l->val_sha1, entry->val_sha1)) die("failed to combine notes %s and %s" " for object %s", sha1_to_hex(l->val_sha1), sha1_to_hex(entry->val_sha1), sha1_to_hex(l->key_sha1)); free(entry); return; } break; case PTR_TYPE_SUBTREE: if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, entry->key_sha1)) { /* unpack 'entry' */ load_subtree(t, entry, tree, n); free(entry); return; } break; } break; case PTR_TYPE_SUBTREE: if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { /* unpack 'l' and restart insert */ *p = NULL; load_subtree(t, l, tree, n); free(l); note_tree_insert(t, tree, n, entry, type, combine_notes); return; } break; } /* non-matching leaf_node */ assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p), combine_notes); *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); note_tree_insert(t, new_node, n + 1, entry, type, combine_notes); }