@matheusbsbs / matheus-bagliano / commits / 2a113aee9b
Fork 0

fast-import: Proper notes tree manipulation

This patch teaches 'git fast-import' to automatically organize note objects in a fast-import stream into an appropriate fanout structure. The notes API in notes.h is NOT used to accomplish this, because trying to keep the fast-import and notes data structures in sync would yield a significantly larger patch with higher complexity. Note objects are added with the 'N' command, and accounted for with a per-branch counter, which is used to trigger fanout restructuring when needed. Note that when restructuring the branch tree, _any_ entry whose path consists of 40 hex chars (not including directory separators) will be recognized as a note object. It is therefore not advisable to manipulate note entries with M/D/R/C commands. Since note objects are stored in the same tree structure as other objects, the unloading and reloading of a fast-import branches handle note objects transparently. This patch has been improved by the following contributions: - Shawn O. Pearce: Several style- and logic-related improvements Cc: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Johan Herland <johan@herland.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johan Herland committed Dec 7, 2009 at 12:27 UTC 2a113aee9ba6ba55fc9a4c714efc958ab0de6510
2 files changed +272 -18
fast-import.c
+129 -5
@@ -245,6 +245,7 @@ struct branch
245 const char *name;
246 struct tree_entry branch_tree;
247 uintmax_t last_commit;
248 + uintmax_t num_notes;
249 unsigned active : 1;
250 unsigned pack_id : PACK_ID_BITS;
251 unsigned char sha1[20];
@@ -693,6 +694,7 @@ static struct branch *new_branch(const char *name)
694 b->table_next_branch = branch_table[hc];
695 b->branch_tree.versions[0].mode = S_IFDIR;
696 b->branch_tree.versions[1].mode = S_IFDIR;
697 + b->num_notes = 0;
698 b->active = 0;
699 b->pack_id = MAX_PACK_ID;
700 branch_table[hc] = b;
@@ -1860,6 +1862,109 @@ static void load_branch(struct branch *b)
1862 }
1863 }
1864
1865 +static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes)
1866 +{
1867 + unsigned char fanout = 0;
1868 + while ((num_notes >>= 8))
1869 + fanout++;
1870 + return fanout;
1871 +}
1872 +
1873 +static void construct_path_with_fanout(const char *hex_sha1,
1874 + unsigned char fanout, char *path)
1875 +{
1876 + unsigned int i = 0, j = 0;
1877 + if (fanout >= 20)
1878 + die("Too large fanout (%u)", fanout);
1879 + while (fanout) {
1880 + path[i++] = hex_sha1[j++];
1881 + path[i++] = hex_sha1[j++];
1882 + path[i++] = '/';
1883 + fanout--;
1884 + }
1885 + memcpy(path + i, hex_sha1 + j, 40 - j);
1886 + path[i + 40 - j] = '\0';
1887 +}
1888 +
1889 +static uintmax_t do_change_note_fanout(
1890 + struct tree_entry *orig_root, struct tree_entry *root,
1891 + char *hex_sha1, unsigned int hex_sha1_len,
1892 + char *fullpath, unsigned int fullpath_len,
1893 + unsigned char fanout)
1894 +{
1895 + struct tree_content *t = root->tree;
1896 + struct tree_entry *e, leaf;
1897 + unsigned int i, tmp_hex_sha1_len, tmp_fullpath_len;
1898 + uintmax_t num_notes = 0;
1899 + unsigned char sha1[20];
1900 + char realpath[60];
1901 +
1902 + for (i = 0; t && i < t->entry_count; i++) {
1903 + e = t->entries[i];
1904 + tmp_hex_sha1_len = hex_sha1_len + e->name->str_len;
1905 + tmp_fullpath_len = fullpath_len;
1906 +
1907 + /*
1908 + * We're interested in EITHER existing note entries (entries
1909 + * with exactly 40 hex chars in path, not including directory
1910 + * separators), OR directory entries that may contain note
1911 + * entries (with < 40 hex chars in path).
1912 + * Also, each path component in a note entry must be a multiple
1913 + * of 2 chars.
1914 + */
1915 + if (!e->versions[1].mode ||
1916 + tmp_hex_sha1_len > 40 ||
1917 + e->name->str_len % 2)
1918 + continue;
1919 +
1920 + /* This _may_ be a note entry, or a subdir containing notes */
1921 + memcpy(hex_sha1 + hex_sha1_len, e->name->str_dat,
1922 + e->name->str_len);
1923 + if (tmp_fullpath_len)
1924 + fullpath[tmp_fullpath_len++] = '/';
1925 + memcpy(fullpath + tmp_fullpath_len, e->name->str_dat,
1926 + e->name->str_len);
1927 + tmp_fullpath_len += e->name->str_len;
1928 + fullpath[tmp_fullpath_len] = '\0';
1929 +
1930 + if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
1931 + /* This is a note entry */
1932 + construct_path_with_fanout(hex_sha1, fanout, realpath);
1933 + if (!strcmp(fullpath, realpath)) {
1934 + /* Note entry is in correct location */
1935 + num_notes++;
1936 + continue;
1937 + }
1938 +
1939 + /* Rename fullpath to realpath */
1940 + if (!tree_content_remove(orig_root, fullpath, &leaf))
1941 + die("Failed to remove path %s", fullpath);
1942 + tree_content_set(orig_root, realpath,
1943 + leaf.versions[1].sha1,
1944 + leaf.versions[1].mode,
1945 + leaf.tree);
1946 + } else if (S_ISDIR(e->versions[1].mode)) {
1947 + /* This is a subdir that may contain note entries */
1948 + if (!e->tree)
1949 + load_tree(e);
1950 + num_notes += do_change_note_fanout(orig_root, e,
1951 + hex_sha1, tmp_hex_sha1_len,
1952 + fullpath, tmp_fullpath_len, fanout);
1953 + }
1954 +
1955 + /* The above may have reallocated the current tree_content */
1956 + t = root->tree;
1957 + }
1958 + return num_notes;
1959 +}
1960 +
1961 +static uintmax_t change_note_fanout(struct tree_entry *root,
1962 + unsigned char fanout)
1963 +{
1964 + char hex_sha1[40], path[60];
1965 + return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
1966 +}
1967 +
1968 static void file_change_m(struct branch *b)
1969 {
1970 const char *p = command_buf.buf + 2;
@@ -2010,14 +2115,16 @@ static void file_change_cr(struct branch *b, int rename)
2115 leaf.tree);
2116 }
2117
2013 -static void note_change_n(struct branch *b)
2118 +static void note_change_n(struct branch *b, unsigned char old_fanout)
2119 {
2120 const char *p = command_buf.buf + 2;
2121 static struct strbuf uq = STRBUF_INIT;
2122 struct object_entry *oe = oe;
2123 struct branch *s;
2124 unsigned char sha1[20], commit_sha1[20];
2125 + char path[60];
2126 uint16_t inline_data = 0;
2127 + unsigned char new_fanout;
2128
2129 /* <dataref> or 'inline' */
2130 if (*p == ':') {
@@ -2071,7 +2178,7 @@ static void note_change_n(struct branch *b)
2178 if (oe->type != OBJ_BLOB)
2179 die("Not a blob (actually a %s): %s",
2180 typename(oe->type), command_buf.buf);
2074 - } else {
2181 + } else if (!is_null_sha1(sha1)) {
2182 enum object_type type = sha1_object_info(sha1, NULL);
2183 if (type < 0)
2184 die("Blob not found: %s", command_buf.buf);
@@ -2080,8 +2187,17 @@ static void note_change_n(struct branch *b)
2187 typename(type), command_buf.buf);
2188 }
2189
2083 - tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,
2084 - S_IFREG | 0644, NULL);
2190 + construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
2191 + if (tree_content_remove(&b->branch_tree, path, NULL))
2192 + b->num_notes--;
2193 +
2194 + if (is_null_sha1(sha1))
2195 + return; /* nothing to insert */
2196 +
2197 + b->num_notes++;
2198 + new_fanout = convert_num_notes_to_fanout(b->num_notes);
2199 + construct_path_with_fanout(sha1_to_hex(commit_sha1), new_fanout, path);
2200 + tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644, NULL);
2201 }
2202
2203 static void file_change_deleteall(struct branch *b)
@@ -2090,6 +2206,7 @@ static void file_change_deleteall(struct branch *b)
2206 hashclr(b->branch_tree.versions[0].sha1);
2207 hashclr(b->branch_tree.versions[1].sha1);
2208 load_tree(&b->branch_tree);
2209 + b->num_notes = 0;
2210 }
2211
2212 static void parse_from_commit(struct branch *b, char *buf, unsigned long size)
@@ -2213,6 +2330,7 @@ static void parse_new_commit(void)
2330 char *committer = NULL;
2331 struct hash_list *merge_list = NULL;
2332 unsigned int merge_count;
2333 + unsigned char prev_fanout, new_fanout;
2334
2335 /* Obtain the branch name from the rest of our command */
2336 sp = strchr(command_buf.buf, ' ') + 1;
@@ -2243,6 +2361,8 @@ static void parse_new_commit(void)
2361 load_branch(b);
2362 }
2363
2364 + prev_fanout = convert_num_notes_to_fanout(b->num_notes);
2365 +
2366 /* file_change* */
2367 while (command_buf.len > 0) {
2368 if (!prefixcmp(command_buf.buf, "M "))
@@ -2254,7 +2374,7 @@ static void parse_new_commit(void)
2374 else if (!prefixcmp(command_buf.buf, "C "))
2375 file_change_cr(b, 0);
2376 else if (!prefixcmp(command_buf.buf, "N "))
2257 - note_change_n(b);
2377 + note_change_n(b, prev_fanout);
2378 else if (!strcmp("deleteall", command_buf.buf))
2379 file_change_deleteall(b);
2380 else {
@@ -2265,6 +2385,10 @@ static void parse_new_commit(void)
2385 break;
2386 }
2387
2388 + new_fanout = convert_num_notes_to_fanout(b->num_notes);
2389 + if (new_fanout != prev_fanout)
2390 + b->num_notes = change_note_fanout(&b->branch_tree, new_fanout);
2391 +
2392 /* build the tree and the commit */
2393 store_tree(&b->branch_tree);
2394 hashcpy(b->branch_tree.versions[0].sha1,
t/t9300-fast-import.sh
+143 -13
@@ -1092,9 +1092,12 @@ test_expect_success 'P: fail on blob mark in gitlink' '
1092 ### series Q (notes)
1093 ###
1094
1095 -note1_data="Note for the first commit"
1096 -note2_data="Note for the second commit"
1097 -note3_data="Note for the third commit"
1095 +note1_data="The first note for the first commit"
1096 +note2_data="The first note for the second commit"
1097 +note3_data="The first note for the third commit"
1098 +note1b_data="The second note for the first commit"
1099 +note1c_data="The third note for the first commit"
1100 +note2b_data="The second note for the second commit"
1101
1102 test_tick
1103 cat >input <<INPUT_END
@@ -1169,7 +1172,45 @@ data <<EOF
1172 $note3_data
1173 EOF
1174
1175 +commit refs/notes/foobar
1176 +mark :10
1177 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1178 +data <<COMMIT
1179 +notes (:10)
1180 +COMMIT
1181 +
1182 +N inline :3
1183 +data <<EOF
1184 +$note1b_data
1185 +EOF
1186 +
1187 +commit refs/notes/foobar2
1188 +mark :11
1189 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1190 +data <<COMMIT
1191 +notes (:11)
1192 +COMMIT
1193 +
1194 +N inline :3
1195 +data <<EOF
1196 +$note1c_data
1197 +EOF
1198 +
1199 +commit refs/notes/foobar
1200 +mark :12
1201 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1202 +data <<COMMIT
1203 +notes (:12)
1204 +COMMIT
1205 +
1206 +deleteall
1207 +N inline :5
1208 +data <<EOF
1209 +$note2b_data
1210 +EOF
1211 +
1212 INPUT_END
1213 +
1214 test_expect_success \
1215 'Q: commit notes' \
1216 'git fast-import <input &&
@@ -1224,8 +1265,8 @@ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1265 notes (:9)
1266 EOF
1267 test_expect_success \
1227 - 'Q: verify notes commit' \
1228 - 'git cat-file commit refs/notes/foobar | sed 1d >actual &&
1268 + 'Q: verify first notes commit' \
1269 + 'git cat-file commit refs/notes/foobar~2 | sed 1d >actual &&
1270 test_cmp expect actual'
1271
1272 cat >expect.unsorted <<EOF
@@ -1235,23 +1276,112 @@ cat >expect.unsorted <<EOF
1276 EOF
1277 cat expect.unsorted | sort >expect
1278 test_expect_success \
1238 - 'Q: verify notes tree' \
1239 - 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
1279 + 'Q: verify first notes tree' \
1280 + 'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
1281 test_cmp expect actual'
1282
1283 echo "$note1_data" >expect
1284 test_expect_success \
1244 - 'Q: verify note for first commit' \
1245 - 'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual'
1285 + 'Q: verify first note for first commit' \
1286 + 'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual'
1287
1288 echo "$note2_data" >expect
1289 test_expect_success \
1249 - 'Q: verify note for second commit' \
1250 - 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
1290 + 'Q: verify first note for second commit' \
1291 + 'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual'
1292 +
1293 +echo "$note3_data" >expect
1294 +test_expect_success \
1295 + 'Q: verify first note for third commit' \
1296 + 'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual'
1297 +
1298 +cat >expect <<EOF
1299 +parent `git rev-parse --verify refs/notes/foobar~2`
1300 +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1301 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1302 +
1303 +notes (:10)
1304 +EOF
1305 +test_expect_success \
1306 + 'Q: verify second notes commit' \
1307 + 'git cat-file commit refs/notes/foobar^ | sed 1d >actual &&
1308 + test_cmp expect actual'
1309 +
1310 +cat >expect.unsorted <<EOF
1311 +100644 blob $commit1
1312 +100644 blob $commit2
1313 +100644 blob $commit3
1314 +EOF
1315 +cat expect.unsorted | sort >expect
1316 +test_expect_success \
1317 + 'Q: verify second notes tree' \
1318 + 'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
1319 + test_cmp expect actual'
1320 +
1321 +echo "$note1b_data" >expect
1322 +test_expect_success \
1323 + 'Q: verify second note for first commit' \
1324 + 'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual'
1325 +
1326 +echo "$note2_data" >expect
1327 +test_expect_success \
1328 + 'Q: verify first note for second commit' \
1329 + 'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual'
1330
1331 echo "$note3_data" >expect
1332 test_expect_success \
1254 - 'Q: verify note for third commit' \
1255 - 'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'
1333 + 'Q: verify first note for third commit' \
1334 + 'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual'
1335 +
1336 +cat >expect <<EOF
1337 +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1338 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1339 +
1340 +notes (:11)
1341 +EOF
1342 +test_expect_success \
1343 + 'Q: verify third notes commit' \
1344 + 'git cat-file commit refs/notes/foobar2 | sed 1d >actual &&
1345 + test_cmp expect actual'
1346 +
1347 +cat >expect.unsorted <<EOF
1348 +100644 blob $commit1
1349 +EOF
1350 +cat expect.unsorted | sort >expect
1351 +test_expect_success \
1352 + 'Q: verify third notes tree' \
1353 + 'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
1354 + test_cmp expect actual'
1355 +
1356 +echo "$note1c_data" >expect
1357 +test_expect_success \
1358 + 'Q: verify third note for first commit' \
1359 + 'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual'
1360 +
1361 +cat >expect <<EOF
1362 +parent `git rev-parse --verify refs/notes/foobar^`
1363 +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1364 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
1365 +
1366 +notes (:12)
1367 +EOF
1368 +test_expect_success \
1369 + 'Q: verify fourth notes commit' \
1370 + 'git cat-file commit refs/notes/foobar | sed 1d >actual &&
1371 + test_cmp expect actual'
1372 +
1373 +cat >expect.unsorted <<EOF
1374 +100644 blob $commit2
1375 +EOF
1376 +cat expect.unsorted | sort >expect
1377 +test_expect_success \
1378 + 'Q: verify fourth notes tree' \
1379 + 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
1380 + test_cmp expect actual'
1381 +
1382 +echo "$note2b_data" >expect
1383 +test_expect_success \
1384 + 'Q: verify second note for second commit' \
1385 + 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
1386
1387 test_done