Skip to content

Commit 7402518

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 Most git commands that you try to run in such a repository with a self-pointing replace object will result in an error: $ 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. Warn the users when we do so, unless they specify --quiet. Signed-off-by: Elijah Newren <[email protected]>
1 parent 8f8d6ee commit 7402518

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)