fast-import: fix file access when run from subdir

In cmd_fast_import(), we ignore the "prefix" argument entirely, even though it tells us how we may have changed directory to the root of the repository earlier in the process. Which means that if you run it from a subdir and point to paths in the filesystem, like: cd subdir git fast-import --import-marks=foo <dump then it will look for "foo" in the root of the repository, not the current directory ("subdir/") which the user would have expected. We can fix this by recording the prefix and using it as appropriate whenever we open a file for reading or writing. I found each of these by looking for cases where we call fopen() within fast-import.c, so this should cover all cases. The new test triggers each one, as well as making sure we don't accidentally apply the prefix when --relative-marks is in use (since that option interprets some paths as relative to a specific directory). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Mar 28, 2023 at 16:54 UTC 9dc607f1c29de88bc5194df49cc7f834e528a3e9
2 files changed +37 -2
builtin/fast-import.c
+8 -2
@@ -176,6 +176,7 @@ static FILE *pack_edges;
176 static unsigned int show_stats = 1;
177 static int global_argc;
178 static const char **global_argv;
179 +static const char *global_prefix;
180
181 /* Memory pools */
182 static struct mem_pool fi_mem_pool = {
@@ -3246,7 +3247,7 @@ static void parse_alias(void)
3247 static char* make_fast_import_path(const char *path)
3248 {
3249 if (!relative_marks_paths || is_absolute_path(path))
3249 - return xstrdup(path);
3250 + return prefix_filename(global_prefix, path);
3251 return git_pathdup("info/fast-import/%s", path);
3252 }
3253
@@ -3317,9 +3318,11 @@ static void option_cat_blob_fd(const char *fd)
3318
3319 static void option_export_pack_edges(const char *edges)
3320 {
3321 + char *fn = prefix_filename(global_prefix, edges);
3322 if (pack_edges)
3323 fclose(pack_edges);
3322 - pack_edges = xfopen(edges, "a");
3324 + pack_edges = xfopen(fn, "a");
3325 + free(fn);
3326 }
3327
3328 static void option_rewrite_submodules(const char *arg, struct string_list *list)
@@ -3334,11 +3337,13 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)
3337 f++;
3338 CALLOC_ARRAY(ms, 1);
3339
3340 + f = prefix_filename(global_prefix, f);
3341 fp = fopen(f, "r");
3342 if (!fp)
3343 die_errno("cannot read '%s'", f);
3344 read_mark_file(&ms, fp, insert_oid_entry);
3345 fclose(fp);
3346 + free(f);
3347
3348 string_list_insert(list, s)->util = ms;
3349 }
@@ -3552,6 +3557,7 @@ int cmd_fast_import(int argc, const char **argv, const char *prefix)
3557
3558 global_argc = argc;
3559 global_argv = argv;
3560 + global_prefix = prefix;
3561
3562 rc_free = mem_pool_alloc(&fi_mem_pool, cmd_save * sizeof(*rc_free));
3563 for (i = 0; i < (cmd_save - 1); i++)
t/t9304-fast-import-marks.sh
+29
@@ -49,4 +49,33 @@ test_expect_success 'import with submodule mapping' '
49 test_cmp expect actual
50 '
51
52 +test_expect_success 'paths adjusted for relative subdir' '
53 + git init deep-dst &&
54 + mkdir deep-dst/subdir &&
55 + >deep-dst/subdir/empty-marks &&
56 + git -C deep-dst/subdir fast-import \
57 + --rewrite-submodules-from=sub:../../from \
58 + --rewrite-submodules-to=sub:../../to \
59 + --import-marks=empty-marks \
60 + --export-marks=exported-marks \
61 + --export-pack-edges=exported-edges \
62 + <dump &&
63 + # we do not bother checking resulting repo; we just care that nothing
64 + # complained about failing to open files for reading, and that files
65 + # for writing were created in the expected spot
66 + test_path_is_file deep-dst/subdir/exported-marks &&
67 + test_path_is_file deep-dst/subdir/exported-edges
68 +'
69 +
70 +test_expect_success 'relative marks are not affected by subdir' '
71 + git init deep-relative &&
72 + mkdir deep-relative/subdir &&
73 + git -C deep-relative/subdir fast-import \
74 + --relative-marks \
75 + --export-marks=exported-marks \
76 + <dump &&
77 + test_path_is_missing deep-relative/subdir/exported-marks &&
78 + test_path_is_file deep-relative/.git/info/fast-import/exported-marks
79 +'
80 +
81 test_done