update-ref: utilize rejected error details if available

When git-update-ref(1) received the '--update-ref' flag, the error details generated in the refs namespace wasn't propagated with failed updates. Instead only an error code pertaining to the type of rejection was noted. This missed detailed error message which the user can act upon. The previous commits added the required code to propagate these detailed error messages from the refs namespace. Now that additional details are available, let's output this additional details to stderr. This allows users to have additional information over the already present machine parsable output. While we're here, improve the existing tests for the machine parsable output by checking for the entire output string and not just the rejection reason. Reported-by: Elijah Newren <newren@gmail.com> Co-authored-by: Jeff King <peff@peff.net> Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Jan 25, 2026 at 23:52 UTC a366bdec0fb06a61d5c42e4047aab0658cec912e
2 files changed +47 -32
builtin/update-ref.c
+5 -3
@@ -573,16 +573,18 @@ static void print_rejected_refs(const char *refname,
573 const char *old_target,
574 const char *new_target,
575 enum ref_transaction_error err,
576 - const char *details UNUSED,
576 + const char *details,
577 void *cb_data UNUSED)
578 {
579 struct strbuf sb = STRBUF_INIT;
580 - const char *reason = ref_transaction_error_msg(err);
580 +
581 + if (details && *details)
582 + error("%s", details);
583
584 strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
585 new_oid ? oid_to_hex(new_oid) : new_target,
586 old_oid ? oid_to_hex(old_oid) : old_target,
585 - reason);
587 + ref_transaction_error_msg(err));
588
589 fwrite(sb.buf, sb.len, 1, stdout);
590 strbuf_release(&sb);
t/t1400-update-ref.sh
+42 -29
@@ -2093,14 +2093,15 @@ do
2093
2094 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2095 format_command $type "update refs/heads/ref2" "$(test_oid 001)" "$head" >>stdin &&
2096 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2096 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2097 echo $old_head >expect &&
2098 git rev-parse refs/heads/ref1 >actual &&
2099 test_cmp expect actual &&
2100 echo $head >expect &&
2101 git rev-parse refs/heads/ref2 >actual &&
2102 test_cmp expect actual &&
2103 - test_grep -q "invalid new value provided" stdout
2103 + test_grep "rejected refs/heads/ref2 $(test_oid 001) $head invalid new value provided" stdout &&
2104 + test_grep "trying to write ref ${SQ}refs/heads/ref2${SQ} with nonexistent object" err
2105 )
2106 '
2107
@@ -2119,14 +2120,15 @@ do
2120
2121 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2122 format_command $type "update refs/heads/ref2" "$head_tree" "$head" >>stdin &&
2122 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2123 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2124 echo $old_head >expect &&
2125 git rev-parse refs/heads/ref1 >actual &&
2126 test_cmp expect actual &&
2127 echo $head >expect &&
2128 git rev-parse refs/heads/ref2 >actual &&
2129 test_cmp expect actual &&
2129 - test_grep -q "invalid new value provided" stdout
2130 + test_grep "rejected refs/heads/ref2 $head_tree $head invalid new value provided" stdout &&
2131 + test_grep "trying to write non-commit object $head_tree to branch ${SQ}refs/heads/ref2${SQ}" err
2132 )
2133 '
2134
@@ -2143,12 +2145,13 @@ do
2145
2146 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2147 format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin &&
2146 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2148 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2149 echo $old_head >expect &&
2150 git rev-parse refs/heads/ref1 >actual &&
2151 test_cmp expect actual &&
2152 test_must_fail git rev-parse refs/heads/ref2 &&
2151 - test_grep -q "reference does not exist" stdout
2153 + test_grep "rejected refs/heads/ref2 $old_head $head reference does not exist" stdout &&
2154 + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: unable to resolve reference ${SQ}refs/heads/ref2${SQ}" err
2155 )
2156 '
2157
@@ -2166,13 +2169,14 @@ do
2169
2170 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2171 format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin &&
2169 - git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout &&
2172 + git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout 2>err &&
2173 echo $old_head >expect &&
2174 git rev-parse refs/heads/ref1 >actual &&
2175 test_cmp expect actual &&
2176 echo $head >expect &&
2177 test_must_fail git rev-parse refs/heads/ref2 &&
2175 - test_grep -q "reference does not exist" stdout
2178 + test_grep "rejected refs/heads/ref2 $old_head $head reference does not exist" stdout &&
2179 + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: reference is missing but expected $head" err
2180 )
2181 '
2182
@@ -2190,7 +2194,7 @@ do
2194
2195 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2196 format_command $type "symref-update refs/heads/ref2" "$old_head" "ref" "refs/heads/nonexistent" >>stdin &&
2193 - git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout &&
2197 + git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout 2>err &&
2198 echo $old_head >expect &&
2199 git rev-parse refs/heads/ref1 >actual &&
2200 test_cmp expect actual &&
@@ -2198,7 +2202,8 @@ do
2202 echo $head >expect &&
2203 git rev-parse refs/heads/ref2 >actual &&
2204 test_cmp expect actual &&
2201 - test_grep -q "expected symref but found regular ref" stdout
2205 + test_grep "rejected refs/heads/ref2 $ZERO_OID $ZERO_OID expected symref but found regular ref" stdout &&
2206 + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: expected symref with target ${SQ}refs/heads/nonexistent${SQ}: but is a regular ref" err
2207 )
2208 '
2209
@@ -2216,14 +2221,15 @@ do
2221
2222 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2223 format_command $type "update refs/heads/ref2" "$old_head" "$Z" >>stdin &&
2219 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2224 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2225 echo $old_head >expect &&
2226 git rev-parse refs/heads/ref1 >actual &&
2227 test_cmp expect actual &&
2228 echo $head >expect &&
2229 git rev-parse refs/heads/ref2 >actual &&
2230 test_cmp expect actual &&
2226 - test_grep -q "reference already exists" stdout
2231 + test_grep "rejected refs/heads/ref2 $old_head $ZERO_OID reference already exists" stdout &&
2232 + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: reference already exists" err
2233 )
2234 '
2235
@@ -2241,14 +2247,15 @@ do
2247
2248 format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
2249 format_command $type "update refs/heads/ref2" "$head" "$old_head" >>stdin &&
2244 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2250 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2251 echo $old_head >expect &&
2252 git rev-parse refs/heads/ref1 >actual &&
2253 test_cmp expect actual &&
2254 echo $head >expect &&
2255 git rev-parse refs/heads/ref2 >actual &&
2256 test_cmp expect actual &&
2251 - test_grep -q "incorrect old value provided" stdout
2257 + test_grep "rejected refs/heads/ref2 $head $old_head incorrect old value provided" stdout &&
2258 + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: is at $head but expected $old_head" err
2259 )
2260 '
2261
@@ -2264,12 +2271,13 @@ do
2271 git update-ref refs/heads/ref/foo $head &&
2272
2273 format_command $type "update refs/heads/ref/foo" "$old_head" "$head" >stdin &&
2267 - format_command $type "update refs/heads/ref" "$old_head" "" >>stdin &&
2268 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2274 + format_command $type "update refs/heads/ref" "$old_head" "$ZERO_OID" >>stdin &&
2275 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2276 echo $old_head >expect &&
2277 git rev-parse refs/heads/ref/foo >actual &&
2278 test_cmp expect actual &&
2272 - test_grep -q "refname conflict" stdout
2279 + test_grep "rejected refs/heads/ref $old_head $ZERO_OID refname conflict" stdout &&
2280 + test_grep "${SQ}refs/heads/ref/foo${SQ} exists; cannot create ${SQ}refs/heads/ref${SQ}" err
2281 )
2282 '
2283
@@ -2284,13 +2292,14 @@ do
2292 head=$(git rev-parse HEAD) &&
2293 git update-ref refs/heads/ref/foo $head &&
2294
2287 - format_command $type "update refs/heads/foo" "$old_head" "" >stdin &&
2288 - format_command $type "update refs/heads/ref" "$old_head" "" >>stdin &&
2289 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2295 + format_command $type "update refs/heads/foo" "$old_head" "$ZERO_OID" >stdin &&
2296 + format_command $type "update refs/heads/ref" "$old_head" "$ZERO_OID" >>stdin &&
2297 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2298 echo $old_head >expect &&
2299 git rev-parse refs/heads/foo >actual &&
2300 test_cmp expect actual &&
2293 - test_grep -q "refname conflict" stdout
2301 + test_grep "rejected refs/heads/ref $old_head $ZERO_OID refname conflict" stdout &&
2302 + test_grep "${SQ}refs/heads/ref/foo${SQ} exists; cannot create ${SQ}refs/heads/ref${SQ}" err
2303 )
2304 '
2305
@@ -2309,14 +2318,15 @@ do
2318 format_command $type "create refs/heads/ref" "$old_head" &&
2319 format_command $type "create refs/heads/Foo" "$old_head"
2320 } >stdin &&
2312 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2321 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2322
2323 echo $head >expect &&
2324 git rev-parse refs/heads/foo >actual &&
2325 echo $old_head >expect &&
2326 git rev-parse refs/heads/ref >actual &&
2327 test_cmp expect actual &&
2319 - test_grep -q "reference conflict due to case-insensitive filesystem" stdout
2328 + test_grep "rejected refs/heads/Foo $old_head $ZERO_OID reference conflict due to case-insensitive filesystem" stdout &&
2329 + test_grep -e "cannot lock ref ${SQ}refs/heads/Foo${SQ}: Unable to create" -e "Foo.lock" err
2330 )
2331 '
2332
@@ -2357,8 +2367,9 @@ do
2367 git symbolic-ref refs/heads/symbolic refs/heads/non-existent &&
2368
2369 format_command $type "delete refs/heads/symbolic" "$head" >stdin &&
2360 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2361 - test_grep "reference does not exist" stdout
2370 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2371 + test_grep "rejected refs/heads/non-existent $ZERO_OID $head reference does not exist" stdout &&
2372 + test_grep "cannot lock ref ${SQ}refs/heads/symbolic${SQ}: unable to resolve reference ${SQ}refs/heads/non-existent${SQ}" err
2373 )
2374 '
2375
@@ -2373,8 +2384,9 @@ do
2384 head=$(git rev-parse HEAD) &&
2385
2386 format_command $type "delete refs/heads/new-branch" "$head" >stdin &&
2376 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2377 - test_grep "incorrect old value provided" stdout
2387 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2388 + test_grep "rejected refs/heads/new-branch $ZERO_OID $head incorrect old value provided" stdout &&
2389 + test_grep "cannot lock ref ${SQ}refs/heads/new-branch${SQ}: is at $(git rev-parse new-branch) but expected $head" err
2390 )
2391 '
2392
@@ -2387,8 +2399,9 @@ do
2399 head=$(git rev-parse HEAD) &&
2400
2401 format_command $type "delete refs/heads/non-existent" "$head" >stdin &&
2390 - git update-ref $type --stdin --batch-updates <stdin >stdout &&
2391 - test_grep "reference does not exist" stdout
2402 + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
2403 + test_grep "rejected refs/heads/non-existent $ZERO_OID $head reference does not exist" stdout &&
2404 + test_grep "cannot lock ref ${SQ}refs/heads/non-existent${SQ}: unable to resolve reference ${SQ}refs/heads/non-existent${SQ}" err
2405 )
2406 '
2407 done