fast-import: add support for new 'alias' command

fast-export and fast-import have nice --import-marks flags which allow for incremental migrations. However, if there is a mark in fast-export's file of marks without a corresponding mark in the one for fast-import, then we run the risk that fast-export tries to send new objects relative to the mark it knows which fast-import does not, causing fast-import to fail. This arises in practice when there is a filter of some sort running between the fast-export and fast-import processes which prunes some commits programmatically. Provide such a filter with the ability to alias pruned commits to their most recent non-pruned ancestor. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Oct 3, 2019 at 13:27 UTC b8f50e5b60e2654fcbf5f72b682635ee2e624923
3 files changed +79 -10
Documentation/git-fast-import.txt
+22
@@ -337,6 +337,13 @@ and control the current import process. More detailed discussion
337 `commit` command. This command is optional and is not
338 needed to perform an import.
339
340 +`alias`::
341 + Record that a mark refers to a given object without first
342 + creating any new object. Using --import-marks and referring
343 + to missing marks will cause fast-import to fail, so aliases
344 + can provide a way to set otherwise pruned commits to a valid
345 + value (e.g. the nearest non-pruned ancestor).
346 +
347 `checkpoint`::
348 Forces fast-import to close the current packfile, generate its
349 unique SHA-1 checksum and index, and start a new packfile.
@@ -914,6 +921,21 @@ a data chunk which does not have an LF as its last byte.
921 +
922 The `LF` after `<delim> LF` is optional (it used to be required).
923
924 +`alias`
925 +~~~~~~~
926 +Record that a mark refers to a given object without first creating any
927 +new object.
928 +
929 +....
930 + 'alias' LF
931 + mark
932 + 'to' SP <commit-ish> LF
933 + LF?
934 +....
935 +
936 +For a detailed description of `<commit-ish>` see above under `from`.
937 +
938 +
939 `checkpoint`
940 ~~~~~~~~~~~~
941 Forces fast-import to close the current packfile, start a new one, and to
fast-import.c
+52 -10
@@ -2491,18 +2491,14 @@ static void parse_from_existing(struct branch *b)
2491 }
2492 }
2493
2494 -static int parse_from(struct branch *b)
2494 +static int parse_objectish(struct branch *b, const char *objectish)
2495 {
2496 - const char *from;
2496 struct branch *s;
2497 struct object_id oid;
2498
2500 - if (!skip_prefix(command_buf.buf, "from ", &from))
2501 - return 0;
2502 -
2499 oidcpy(&oid, &b->branch_tree.versions[1].oid);
2500
2505 - s = lookup_branch(from);
2501 + s = lookup_branch(objectish);
2502 if (b == s)
2503 die("Can't create a branch from itself: %s", b->name);
2504 else if (s) {
@@ -2510,8 +2506,8 @@ static int parse_from(struct branch *b)
2506 oidcpy(&b->oid, &s->oid);
2507 oidcpy(&b->branch_tree.versions[0].oid, t);
2508 oidcpy(&b->branch_tree.versions[1].oid, t);
2513 - } else if (*from == ':') {
2514 - uintmax_t idnum = parse_mark_ref_eol(from);
2509 + } else if (*objectish == ':') {
2510 + uintmax_t idnum = parse_mark_ref_eol(objectish);
2511 struct object_entry *oe = find_mark(idnum);
2512 if (oe->type != OBJ_COMMIT)
2513 die("Mark :%" PRIuMAX " not a commit", idnum);
@@ -2525,13 +2521,13 @@ static int parse_from(struct branch *b)
2521 } else
2522 parse_from_existing(b);
2523 }
2528 - } else if (!get_oid(from, &b->oid)) {
2524 + } else if (!get_oid(objectish, &b->oid)) {
2525 parse_from_existing(b);
2526 if (is_null_oid(&b->oid))
2527 b->delete = 1;
2528 }
2529 else
2534 - die("Invalid ref name or SHA1 expression: %s", from);
2530 + die("Invalid ref name or SHA1 expression: %s", objectish);
2531
2532 if (b->branch_tree.tree && !oideq(&oid, &b->branch_tree.versions[1].oid)) {
2533 release_tree_content_recursive(b->branch_tree.tree);
@@ -2542,6 +2538,26 @@ static int parse_from(struct branch *b)
2538 return 1;
2539 }
2540
2541 +static int parse_from(struct branch *b)
2542 +{
2543 + const char *from;
2544 +
2545 + if (!skip_prefix(command_buf.buf, "from ", &from))
2546 + return 0;
2547 +
2548 + return parse_objectish(b, from);
2549 +}
2550 +
2551 +static int parse_objectish_with_prefix(struct branch *b, const char *prefix)
2552 +{
2553 + const char *base;
2554 +
2555 + if (!skip_prefix(command_buf.buf, prefix, &base))
2556 + return 0;
2557 +
2558 + return parse_objectish(b, base);
2559 +}
2560 +
2561 static struct hash_list *parse_merge(unsigned int *count)
2562 {
2563 struct hash_list *list = NULL, **tail = &list, *n;
@@ -3087,6 +3103,28 @@ static void parse_progress(void)
3103 skip_optional_lf();
3104 }
3105
3106 +static void parse_alias(void)
3107 +{
3108 + struct object_entry *e;
3109 + struct branch b;
3110 +
3111 + skip_optional_lf();
3112 + read_next_command();
3113 +
3114 + /* mark ... */
3115 + parse_mark();
3116 + if (!next_mark)
3117 + die(_("Expected 'mark' command, got %s"), command_buf.buf);
3118 +
3119 + /* to ... */
3120 + memset(&b, 0, sizeof(b));
3121 + if (!parse_objectish_with_prefix(&b, "to "))
3122 + die(_("Expected 'to' command, got %s"), command_buf.buf);
3123 + e = find_object(&b.oid);
3124 + assert(e);
3125 + insert_mark(next_mark, e);
3126 +}
3127 +
3128 static char* make_fast_import_path(const char *path)
3129 {
3130 if (!relative_marks_paths || is_absolute_path(path))
@@ -3214,6 +3252,8 @@ static int parse_one_feature(const char *feature, int from_stream)
3252 option_import_marks(arg, from_stream, 1);
3253 } else if (skip_prefix(feature, "export-marks=", &arg)) {
3254 option_export_marks(arg);
3255 + } else if (!strcmp(feature, "alias")) {
3256 + ; /* Don't die - this feature is supported */
3257 } else if (!strcmp(feature, "get-mark")) {
3258 ; /* Don't die - this feature is supported */
3259 } else if (!strcmp(feature, "cat-blob")) {
@@ -3370,6 +3410,8 @@ int cmd_main(int argc, const char **argv)
3410 parse_checkpoint();
3411 else if (!strcmp("done", command_buf.buf))
3412 break;
3413 + else if (!strcmp("alias", command_buf.buf))
3414 + parse_alias();
3415 else if (starts_with(command_buf.buf, "progress "))
3416 parse_progress();
3417 else if (skip_prefix(command_buf.buf, "feature ", &v))
t/t9300-fast-import.sh
+5
@@ -111,6 +111,10 @@ test_expect_success 'A: create pack from stdin' '
111 Tag of tag of our lovely commit
112 EOF
113
114 + alias
115 + mark :8
116 + to :5
117 +
118 INPUT_END
119 git fast-import --export-marks=marks.out <input &&
120 git whatchanged master
@@ -195,6 +199,7 @@ test_expect_success 'A: verify marks output' '
199 :5 $(git rev-parse --verify master^0)
200 :6 $(git cat-file tag nested | grep object | cut -d" " -f 2)
201 :7 $(git rev-parse --verify nested)
202 + :8 $(git rev-parse --verify master^0)
203 EOF
204 test_cmp expect marks.out
205 '