Skip to content

Commit cc77a00

Browse files
committed
fast-import: avoid making replace refs point to themselves
If someone replaces a commit with a modified version, then builds on that commit, and then later decides to rewrite history in a format like git fast-export --all | CMD_TO_TWEAK_THE_STREAM | git fast-import and CMD_TO_TWEAK_THE_STREAM undoes the modifications that the replacement did, then at the end you'd get a replace ref that points to itself. For example: $ git show-ref | grep replace fb92ebc654641b310e7d0360d0a5a49316fd7264 refs/replace/fb92ebc654641b310e7d0360d0a5a49316fd7264 Git commands which pay attention to replace refs will die with an error when a self-referencing replace ref is present: $ git log fatal: replace depth too high for object fb92ebc654641b310e7d0360d0a5a49316fd7264 Avoid such problems by deleting replace refs that will simply end up pointing to themselves at the end of our writing. Unless users specify --quiet, warn them when we delete such a replace ref. Two notes about this patch: * We are not ignoring the problematic update of the replace ref (turning it into a no-op), we are replacing the update with a delete. The logic here is that if the repository had a value for the replace ref before fast-import was run, and the replace ref was explicitly named in the fast-import stream, we don't want the replace ref to be left with a pre-fast-import value. * While loops with more than one element (e.g. refs/replace/A points to B, and refs/replace/B points to A) are possible, they seem much less plausible. It is pretty easy to create a sequence of git-filter-repo commands that will trigger a self-referencing replace ref, but I do not know how to trigger a scenario with a cycle length greater than 1. Signed-off-by: Elijah Newren <newren@gmail.com>
1 parent 8f8d6ee commit cc77a00

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

builtin/fast-import.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ static unsigned long branch_load_count;
179179
static int failure;
180180
static FILE *pack_edges;
181181
static unsigned int show_stats = 1;
182+
static unsigned int quiet;
182183
static int global_argc;
183184
static const char **global_argv;
184185
static const char *global_prefix;
@@ -1602,7 +1603,19 @@ static int update_branch(struct branch *b)
16021603
struct ref_transaction *transaction;
16031604
struct object_id old_oid;
16041605
struct strbuf err = STRBUF_INIT;
1605-
1606+
static const char *replace_prefix = "refs/replace/";
1607+
1608+
if (starts_with(b->name, replace_prefix) &&
1609+
!strcmp(b->name + strlen(replace_prefix),
1610+
oid_to_hex(&b->oid))) {
1611+
if (!quiet)
1612+
warning("Dropping %s since it would point to "
1613+
"itself (i.e. to %s)",
1614+
b->name, oid_to_hex(&b->oid));
1615+
refs_delete_ref(get_main_ref_store(the_repository),
1616+
NULL, b->name, NULL, 0);
1617+
return 0;
1618+
}
16061619
if (is_null_oid(&b->oid)) {
16071620
if (b->delete)
16081621
refs_delete_ref(get_main_ref_store(the_repository),
@@ -3388,6 +3401,7 @@ static int parse_one_option(const char *option)
33883401
option_export_pack_edges(option);
33893402
} else if (!strcmp(option, "quiet")) {
33903403
show_stats = 0;
3404+
quiet = 1;
33913405
} else if (!strcmp(option, "stats")) {
33923406
show_stats = 1;
33933407
} else if (!strcmp(option, "allow-unsafe-features")) {

t/t9300-fast-import.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3692,6 +3692,34 @@ test_expect_success ICONV 'X: handling encoding' '
36923692
git log -1 --format=%B encoding | grep $(printf "\317\200")
36933693
'
36943694

3695+
test_expect_success 'X: replace ref that becomes useless is removed' '
3696+
git init -qb main testrepo &&
3697+
cd testrepo &&
3698+
(
3699+
test_commit test &&
3700+
3701+
test_commit msg somename content &&
3702+
3703+
git mv somename othername &&
3704+
NEW_TREE=$(git write-tree) &&
3705+
MSG="$(git log -1 --format=%B HEAD)" &&
3706+
NEW_COMMIT=$(git commit-tree -p HEAD^1 -m "$MSG" $NEW_TREE) &&
3707+
git replace main $NEW_COMMIT &&
3708+
3709+
echo more >>othername &&
3710+
git add othername &&
3711+
git commit -qm more &&
3712+
3713+
git fast-export --all >tmp &&
3714+
sed -e s/othername/somename/ tmp >tmp2 &&
3715+
git fast-import --force <tmp2 2>msgs &&
3716+
3717+
grep "Dropping.*since it would point to itself" msgs &&
3718+
git show-ref >refs &&
3719+
! grep refs/replace refs
3720+
)
3721+
'
3722+
36953723
###
36963724
### series Y (submodules and hash algorithms)
36973725
###

0 commit comments

Comments
 (0)