From d5386da2064d16d0857ad30a7507f1eba1599909 Mon Sep 17 00:00:00 2001 From: Tao Klerks Date: Sun, 20 Apr 2025 16:39:18 +0200 Subject: [PATCH] replace-refs: fix support of qualified replace ref paths The enactment of replace refs in replace-object.c#register_replace_ref() explicitly uses the last portion of the ref "path", the substring after the last slash, as the object ID to be replaced. This means that replace refs can be organized into different paths; you can separate replace refs created for different purposes, like "refs/replace/2012-migration/*". This in turn makes it practical to store prepared replace refs in different ref paths on a git server, and have users "map" them, via specific refspecs, into their local repos; different types of replacements can be mapped into different sub-paths of refs/replace/. The only way this didn't "work" is in the commit decoration process, in log-tree.c#add_ref_decoration(), where different logic was used to obtain the replaced object ID, removing the "refs/replace/" prefix only. This inconsistent logic meant that more structured replace ref paths caused a warning to be printed, and the "replaced" decoration to be omitted. Fix this decoration logic to use the same "last part of ref path" logic, fixing spurious warnings (and missing decorations) for users of more structured replace ref paths. Also add tests for qualified replace ref paths. Signed-off-by: Tao Klerks --- log-tree.c | 5 +++-- t/t4207-log-decoration-colors.sh | 17 +++++++++++++++++ t/t6050-replace.sh | 10 ++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/log-tree.c b/log-tree.c index a4d4ab59ca0714..6a87724527b026 100644 --- a/log-tree.c +++ b/log-tree.c @@ -163,10 +163,11 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED, if (starts_with(refname, git_replace_ref_base)) { struct object_id original_oid; + const char *slash = strrchr(refname, '/'); + const char *hash = slash ? slash + 1 : refname; if (!replace_refs_enabled(the_repository)) return 0; - if (get_oid_hex(refname + strlen(git_replace_ref_base), - &original_oid)) { + if (get_oid_hex(hash, &original_oid)) { warning("invalid replace ref %s", refname); return 0; } diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh index 2e83cc820a145e..f80684efcff137 100755 --- a/t/t4207-log-decoration-colors.sh +++ b/t/t4207-log-decoration-colors.sh @@ -131,4 +131,21 @@ ${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A cmp_filtered_decorations ' +test_expect_success 'test replace decoration for nested replace path' ' + test_when_finished remove_replace_refs && + + CURRENT_HASH=$(git rev-parse --verify HEAD) && + git replace --graft HEAD HEAD~2 && + git update-ref refs/tmp/tmpref refs/replace/$CURRENT_HASH && + git update-ref -d refs/replace/$CURRENT_HASH && + git update-ref refs/replace/nested-path/abc/$CURRENT_HASH refs/tmp/tmpref && + + git log --decorate -1 HEAD >actual && + test_grep "replaced" actual && + + git --no-replace-objects log --decorate -1 HEAD >actual && + test_grep ! "replaced" actual + +' + test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index aa1b5351873ee5..9b06baa1439e58 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -546,4 +546,14 @@ test_expect_success '--convert-graft-file' ' test_grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts ' +test_expect_success 'replace ref in a nested path' ' + git cat-file commit $HASH2 >actual && + R=$(sed -e "s/A U/O/" actual | git hash-object -t commit --stdin -w) && + git update-ref refs/replace/abc1/$HASH2 $R && + git show $HASH2 >actual && + test_grep "O Thor" actual && + git --no-replace-objects show $HASH2 >actual && + test_grep "A U Thor" actual +' + test_done