| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='test log -L' |
| 4 | GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main |
| 5 | export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME |
| 6 | |
| 7 | . ./test-lib.sh |
| 8 | |
| 9 | test_expect_success 'setup (import history)' ' |
| 10 | git fast-import < "$TEST_DIRECTORY"/t4211/history.export && |
| 11 | git reset --hard |
| 12 | ' |
| 13 | |
| 14 | test_expect_success 'basic command line parsing' ' |
| 15 | # This may fail due to "no such path a.c in commit", or |
| 16 | # "-L is incompatible with pathspec", depending on the |
| 17 | # order the error is checked. Either is acceptable. |
| 18 | test_must_fail git log -L1,1:a.c -- a.c && |
| 19 | |
| 20 | # -L requires there is no pathspec |
| 21 | test_must_fail git log -L1,1:b.c -- b.c 2>error && |
| 22 | test_grep "cannot be used with pathspec" error && |
| 23 | |
| 24 | # This would fail because --follow wants a single path, but |
| 25 | # we may fail due to incompatibility between -L/--follow in |
| 26 | # the future. Either is acceptable. |
| 27 | test_must_fail git log -L1,1:b.c --follow && |
| 28 | test_must_fail git log --follow -L1,1:b.c && |
| 29 | |
| 30 | # This would fail because -L wants no pathspec, but |
| 31 | # we may fail due to incompatibility between -L/--follow in |
| 32 | # the future. Either is acceptable. |
| 33 | test_must_fail git log --follow -L1,1:b.c -- b.c |
| 34 | ' |
| 35 | |
| 36 | canned_test_1 () { |
| 37 | test_expect_$1 "$2" " |
| 38 | git log $2 >actual && |
| 39 | test_cmp \"\$TEST_DIRECTORY\"/t4211/$(test_oid algo)/expect.$3 actual |
| 40 | " |
| 41 | } |
| 42 | |
| 43 | canned_test () { |
| 44 | canned_test_1 success "$@" |
| 45 | } |
| 46 | canned_test_failure () { |
| 47 | canned_test_1 failure "$@" |
| 48 | } |
| 49 | |
| 50 | test_bad_opts () { |
| 51 | test_expect_success "invalid args: $1" " |
| 52 | test_must_fail git log $1 2>errors && |
| 53 | test_grep '$2' errors |
| 54 | " |
| 55 | } |
| 56 | |
| 57 | canned_test "-L 4,12:a.c simple" simple-f |
| 58 | canned_test "-L 4,+9:a.c simple" simple-f |
| 59 | canned_test "-L '/long f/,/^}/:a.c' simple" simple-f |
| 60 | canned_test "-L :f:a.c simple" simple-f-to-main |
| 61 | |
| 62 | canned_test "-L '/main/,/^}/:a.c' simple" simple-main |
| 63 | canned_test "-L :main:a.c simple" simple-main-to-end |
| 64 | |
| 65 | canned_test "-L 1,+4:a.c simple" beginning-of-file |
| 66 | |
| 67 | canned_test "-L 20:a.c simple" end-of-file |
| 68 | |
| 69 | canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges |
| 70 | canned_test "-L 24,+1:a.c simple" vanishes-early |
| 71 | |
| 72 | canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f |
| 73 | canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main |
| 74 | |
| 75 | canned_test "-L 4,12:a.c -L :main:a.c simple" multiple |
| 76 | canned_test "-L 4,18:a.c -L ^:main:a.c simple" multiple-overlapping |
| 77 | canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping |
| 78 | canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset |
| 79 | canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset |
| 80 | |
| 81 | canned_test "-L 10,16:b.c -L 18,26:b.c main" no-assertion-error |
| 82 | |
| 83 | test_bad_opts "-L" "switch.*requires a value" |
| 84 | test_bad_opts "-L b.c" "argument not .start,end:file" |
| 85 | test_bad_opts "-L 1:" "argument not .start,end:file" |
| 86 | test_bad_opts "-L 1:nonexistent" "There is no path" |
| 87 | test_bad_opts "-L 1:simple" "There is no path" |
| 88 | test_bad_opts "-L '/foo:b.c'" "argument not .start,end:file" |
| 89 | test_bad_opts "-L 1000:b.c" "has only.*lines" |
| 90 | test_bad_opts "-L :b.c" "argument not .start,end:file" |
| 91 | test_bad_opts "-L :foo:b.c" "no match" |
| 92 | |
| 93 | test_expect_success '-L X (X == nlines)' ' |
| 94 | n=$(wc -l <b.c) && |
| 95 | git log -L $n:b.c |
| 96 | ' |
| 97 | |
| 98 | test_expect_success '-L X (X == nlines + 1)' ' |
| 99 | n=$(expr $(wc -l <b.c) + 1) && |
| 100 | test_must_fail git log -L $n:b.c |
| 101 | ' |
| 102 | |
| 103 | test_expect_success '-L X (X == nlines + 2)' ' |
| 104 | n=$(expr $(wc -l <b.c) + 2) && |
| 105 | test_must_fail git log -L $n:b.c |
| 106 | ' |
| 107 | |
| 108 | test_expect_success '-L ,Y (Y == nlines)' ' |
| 109 | n=$(printf "%d" $(wc -l <b.c)) && |
| 110 | git log -L ,$n:b.c |
| 111 | ' |
| 112 | |
| 113 | test_expect_success '-L ,Y (Y == nlines + 1)' ' |
| 114 | n=$(expr $(wc -l <b.c) + 1) && |
| 115 | git log -L ,$n:b.c |
| 116 | ' |
| 117 | |
| 118 | test_expect_success '-L ,Y (Y == nlines + 2)' ' |
| 119 | n=$(expr $(wc -l <b.c) + 2) && |
| 120 | git log -L ,$n:b.c |
| 121 | ' |
| 122 | |
| 123 | test_expect_success '-L with --first-parent and a merge' ' |
| 124 | git checkout parallel-change && |
| 125 | git log --first-parent -L 1,1:b.c |
| 126 | ' |
| 127 | |
| 128 | test_expect_success '-L with --output' ' |
| 129 | git checkout parallel-change && |
| 130 | git log --output=log -L :main:b.c >output && |
| 131 | test_must_be_empty output && |
| 132 | test_line_count = 75 log |
| 133 | ' |
| 134 | |
| 135 | test_expect_success 'range_set_union' ' |
| 136 | test_seq 500 > c.c && |
| 137 | git add c.c && |
| 138 | git commit -m "many lines" && |
| 139 | test_seq 1000 > c.c && |
| 140 | git add c.c && |
| 141 | git commit -m "modify many lines" && |
| 142 | git log $(for x in $(test_seq 200); do echo -L $((2*x)),+1:c.c || return 1; done) |
| 143 | ' |
| 144 | |
| 145 | test_expect_success '-s shows only line-log commits' ' |
| 146 | git log --format="commit %s" -L1,24:b.c >expect.raw && |
| 147 | grep ^commit expect.raw >expect && |
| 148 | git log --format="commit %s" -L1,24:b.c -s >actual && |
| 149 | test_cmp expect actual |
| 150 | ' |
| 151 | |
| 152 | test_expect_success '-p shows the default patch output' ' |
| 153 | git log -L1,24:b.c >expect && |
| 154 | git log -L1,24:b.c -p >actual && |
| 155 | test_cmp expect actual |
| 156 | ' |
| 157 | |
| 158 | test_expect_success '--raw shows mode, oid, status and path' ' |
| 159 | git log -L1,24:b.c --raw --format= >actual && |
| 160 | test_grep "^:100644 100644 [0-9a-f]\{7\} [0-9a-f]\{7\} M b.c$" actual && |
| 161 | test_grep ! "^diff --git" actual && |
| 162 | test_grep ! "^@@" actual |
| 163 | ' |
| 164 | |
| 165 | test_expect_success '--name-only shows path' ' |
| 166 | git log -L1,24:b.c --name-only --format= >actual && |
| 167 | test_grep "^b.c$" actual && |
| 168 | test_grep ! "^diff --git" actual && |
| 169 | test_grep ! "^@@" actual |
| 170 | ' |
| 171 | |
| 172 | test_expect_success '--name-status shows status and path' ' |
| 173 | git log -L1,24:b.c --name-status --format= >actual && |
| 174 | test_grep "^M b.c$" actual && |
| 175 | test_grep ! "^diff --git" actual && |
| 176 | test_grep ! "^@@" actual |
| 177 | ' |
| 178 | |
| 179 | test_expect_success '--dirstat is not supported with -L' ' |
| 180 | # --dirstat is not supported with -L: its default mode measures |
| 181 | # whole-file change, not the tracked lines, and the |
| 182 | # --dirstat=lines variant is deferred too, so both forms are |
| 183 | # rejected like any other unsupported format. |
| 184 | test_must_fail git log -L1,24:b.c --dirstat 2>err && |
| 185 | test_grep "does not support" err && |
| 186 | test_must_fail git log -L1,24:b.c --dirstat=lines 2>err && |
| 187 | test_grep "does not support" err |
| 188 | ' |
| 189 | |
| 190 | test_expect_success 'setup for checking fancy rename following' ' |
| 191 | git checkout --orphan moves-start && |
| 192 | git reset --hard && |
| 193 | |
| 194 | printf "%s\n" 12 13 14 15 b c d e >file-1 && |
| 195 | printf "%s\n" 22 23 24 25 B C D E >file-2 && |
| 196 | git add file-1 file-2 && |
| 197 | test_tick && |
| 198 | git commit -m "Add file-1 and file-2" && |
| 199 | oid_add_f1_f2=$(git rev-parse --short HEAD) && |
| 200 | |
| 201 | git checkout -b moves-main && |
| 202 | printf "%s\n" 11 12 13 14 15 b c d e >file-1 && |
| 203 | git commit -a -m "Modify file-1 on main" && |
| 204 | oid_mod_f1_main=$(git rev-parse --short HEAD) && |
| 205 | |
| 206 | printf "%s\n" 21 22 23 24 25 B C D E >file-2 && |
| 207 | git commit -a -m "Modify file-2 on main #1" && |
| 208 | oid_mod_f2_main_1=$(git rev-parse --short HEAD) && |
| 209 | |
| 210 | git mv file-1 renamed-1 && |
| 211 | git commit -m "Rename file-1 to renamed-1 on main" && |
| 212 | |
| 213 | printf "%s\n" 11 12 13 14 15 b c d e f >renamed-1 && |
| 214 | git commit -a -m "Modify renamed-1 on main" && |
| 215 | oid_mod_r1_main=$(git rev-parse --short HEAD) && |
| 216 | |
| 217 | printf "%s\n" 21 22 23 24 25 B C D E F >file-2 && |
| 218 | git commit -a -m "Modify file-2 on main #2" && |
| 219 | oid_mod_f2_main_2=$(git rev-parse --short HEAD) && |
| 220 | |
| 221 | git checkout -b moves-side moves-start && |
| 222 | printf "%s\n" 12 13 14 15 16 b c d e >file-1 && |
| 223 | git commit -a -m "Modify file-1 on side #1" && |
| 224 | oid_mod_f1_side_1=$(git rev-parse --short HEAD) && |
| 225 | |
| 226 | printf "%s\n" 22 23 24 25 26 B C D E >file-2 && |
| 227 | git commit -a -m "Modify file-2 on side" && |
| 228 | oid_mod_f2_side=$(git rev-parse --short HEAD) && |
| 229 | |
| 230 | git mv file-2 renamed-2 && |
| 231 | git commit -m "Rename file-2 to renamed-2 on side" && |
| 232 | |
| 233 | printf "%s\n" 12 13 14 15 16 a b c d e >file-1 && |
| 234 | git commit -a -m "Modify file-1 on side #2" && |
| 235 | oid_mod_f1_side_2=$(git rev-parse --short HEAD) && |
| 236 | |
| 237 | printf "%s\n" 22 23 24 25 26 A B C D E >renamed-2 && |
| 238 | git commit -a -m "Modify renamed-2 on side" && |
| 239 | oid_mod_r2_side=$(git rev-parse --short HEAD) && |
| 240 | |
| 241 | git checkout moves-main && |
| 242 | git merge moves-side && |
| 243 | oid_merge=$(git rev-parse --short HEAD) |
| 244 | ' |
| 245 | |
| 246 | test_expect_success 'fancy rename following #1' ' |
| 247 | cat >expect <<-EOF && |
| 248 | $oid_merge Merge branch '\''moves-side'\'' into moves-main |
| 249 | $oid_mod_f1_side_2 Modify file-1 on side #2 |
| 250 | $oid_mod_f1_side_1 Modify file-1 on side #1 |
| 251 | $oid_mod_r1_main Modify renamed-1 on main |
| 252 | $oid_mod_f1_main Modify file-1 on main |
| 253 | $oid_add_f1_f2 Add file-1 and file-2 |
| 254 | EOF |
| 255 | git log -L1:renamed-1 --oneline --no-patch >actual && |
| 256 | test_cmp expect actual |
| 257 | ' |
| 258 | |
| 259 | test_expect_success 'fancy rename following #2' ' |
| 260 | cat >expect <<-EOF && |
| 261 | $oid_merge Merge branch '\''moves-side'\'' into moves-main |
| 262 | $oid_mod_r2_side Modify renamed-2 on side |
| 263 | $oid_mod_f2_side Modify file-2 on side |
| 264 | $oid_mod_f2_main_2 Modify file-2 on main #2 |
| 265 | $oid_mod_f2_main_1 Modify file-2 on main #1 |
| 266 | $oid_add_f1_f2 Add file-1 and file-2 |
| 267 | EOF |
| 268 | git log -L1:renamed-2 --oneline --no-patch >actual && |
| 269 | test_cmp expect actual |
| 270 | ' |
| 271 | |
| 272 | # Create the following linear history, where each commit does what its |
| 273 | # subject line promises: |
| 274 | # |
| 275 | # * 66c6410 Modify func2() in file.c |
| 276 | # * 50834e5 Modify other-file |
| 277 | # * fe5851c Modify func1() in file.c |
| 278 | # * 8c7c7dd Add other-file |
| 279 | # * d5f4417 Add func1() and func2() in file.c |
| 280 | test_expect_success 'setup for checking line-log and parent oids' ' |
| 281 | git checkout --orphan parent-oids && |
| 282 | git reset --hard && |
| 283 | |
| 284 | cat >file.c <<-\EOF && |
| 285 | int func1() |
| 286 | { |
| 287 | return F1; |
| 288 | } |
| 289 | |
| 290 | int func2() |
| 291 | { |
| 292 | return F2; |
| 293 | } |
| 294 | EOF |
| 295 | git add file.c && |
| 296 | test_tick && |
| 297 | first_tick=$test_tick && |
| 298 | git commit -m "Add func1() and func2() in file.c" && |
| 299 | |
| 300 | echo 1 >other-file && |
| 301 | git add other-file && |
| 302 | test_tick && |
| 303 | git commit -m "Add other-file" && |
| 304 | |
| 305 | sed -e "s/F1/F1 + 1/" file.c >tmp && |
| 306 | mv tmp file.c && |
| 307 | git commit -a -m "Modify func1() in file.c" && |
| 308 | |
| 309 | echo 2 >other-file && |
| 310 | git commit -a -m "Modify other-file" && |
| 311 | |
| 312 | sed -e "s/F2/F2 + 2/" file.c >tmp && |
| 313 | mv tmp file.c && |
| 314 | git commit -a -m "Modify func2() in file.c" && |
| 315 | |
| 316 | head_oid=$(git rev-parse --short HEAD) && |
| 317 | prev_oid=$(git rev-parse --short HEAD^) && |
| 318 | root_oid=$(git rev-parse --short HEAD~4) |
| 319 | ' |
| 320 | |
| 321 | # Parent oid should be from immediate parent. |
| 322 | test_expect_success 'parent oids without parent rewriting' ' |
| 323 | cat >expect <<-EOF && |
| 324 | $head_oid $prev_oid Modify func2() in file.c |
| 325 | $root_oid Add func1() and func2() in file.c |
| 326 | EOF |
| 327 | git log --format="%h %p %s" --no-patch -L:func2:file.c >actual && |
| 328 | test_cmp expect actual |
| 329 | ' |
| 330 | |
| 331 | # Parent oid should be from the most recent ancestor touching func2(), |
| 332 | # i.e. in this case from the root commit. |
| 333 | test_expect_success 'parent oids with parent rewriting' ' |
| 334 | cat >expect <<-EOF && |
| 335 | $head_oid $root_oid Modify func2() in file.c |
| 336 | $root_oid Add func1() and func2() in file.c |
| 337 | EOF |
| 338 | git log --format="%h %p %s" --no-patch -L:func2:file.c --parents >actual && |
| 339 | test_cmp expect actual |
| 340 | ' |
| 341 | |
| 342 | test_expect_success 'line-log with --before' ' |
| 343 | echo $root_oid >expect && |
| 344 | git log --format=%h --no-patch -L:func2:file.c --before=$first_tick >actual && |
| 345 | test_cmp expect actual |
| 346 | ' |
| 347 | |
| 348 | test_expect_success 'setup tests for zero-width regular expressions' ' |
| 349 | cat >expect <<-EOF |
| 350 | Modify func1() in file.c |
| 351 | Add func1() and func2() in file.c |
| 352 | EOF |
| 353 | ' |
| 354 | |
| 355 | test_expect_success 'zero-width regex $ matches any function name' ' |
| 356 | git log --format="%s" --no-patch "-L:$:file.c" >actual && |
| 357 | test_cmp expect actual |
| 358 | ' |
| 359 | |
| 360 | test_expect_success 'zero-width regex ^ matches any function name' ' |
| 361 | git log --format="%s" --no-patch "-L:^:file.c" >actual && |
| 362 | test_cmp expect actual |
| 363 | ' |
| 364 | |
| 365 | test_expect_success 'zero-width regex .* matches any function name' ' |
| 366 | git log --format="%s" --no-patch "-L:.*:file.c" >actual && |
| 367 | test_cmp expect actual |
| 368 | ' |
| 369 | |
| 370 | test_expect_success 'setup for diff pipeline tests' ' |
| 371 | git checkout parent-oids && |
| 372 | |
| 373 | head_blob_old=$(git rev-parse --short HEAD^:file.c) && |
| 374 | head_blob_new=$(git rev-parse --short HEAD:file.c) && |
| 375 | root_blob=$(git rev-parse --short HEAD~4:file.c) && |
| 376 | null_blob=$(test_oid zero | cut -c1-7) && |
| 377 | head_blob_old_full=$(git rev-parse HEAD^:file.c) && |
| 378 | head_blob_new_full=$(git rev-parse HEAD:file.c) && |
| 379 | root_blob_full=$(git rev-parse HEAD~4:file.c) && |
| 380 | null_blob_full=$(test_oid zero) |
| 381 | ' |
| 382 | |
| 383 | test_expect_success '-L diff output includes index and new file mode' ' |
| 384 | git log -L:func2:file.c --format= >actual && |
| 385 | |
| 386 | # Output should contain index headers (not present in old code path) |
| 387 | test_grep "^index $head_blob_old\.\.$head_blob_new 100644" actual && |
| 388 | |
| 389 | # Root commit should show new file mode and null index |
| 390 | test_grep "^new file mode 100644" actual && |
| 391 | test_grep "^index $null_blob\.\.$root_blob$" actual && |
| 392 | |
| 393 | # Hunk headers should include funcname context |
| 394 | test_grep "^@@ .* @@ int func1()" actual |
| 395 | ' |
| 396 | |
| 397 | test_expect_success '-L with --word-diff' ' |
| 398 | cat >expect <<-\EOF && |
| 399 | diff --git a/file.c b/file.c |
| 400 | --- a/file.c |
| 401 | +++ b/file.c |
| 402 | @@ -6,4 +6,4 @@ int func1() |
| 403 | int func2() |
| 404 | { |
| 405 | return [-F2;-]{+F2 + 2;+} |
| 406 | } |
| 407 | diff --git a/file.c b/file.c |
| 408 | new file mode 100644 |
| 409 | --- /dev/null |
| 410 | +++ b/file.c |
| 411 | @@ -0,0 +6,4 @@ |
| 412 | {+int func2()+} |
| 413 | {+{+} |
| 414 | {+ return F2;+} |
| 415 | {+}+} |
| 416 | EOF |
| 417 | git log -L:func2:file.c --word-diff --format= >actual && |
| 418 | grep -v "^index " actual >actual.filtered && |
| 419 | grep -v "^index " expect >expect.filtered && |
| 420 | test_cmp expect.filtered actual.filtered |
| 421 | ' |
| 422 | |
| 423 | test_expect_success '-L with --no-prefix' ' |
| 424 | git log -L:func2:file.c --no-prefix --format= >actual && |
| 425 | test_grep "^diff --git file.c file.c" actual && |
| 426 | test_grep "^--- file.c" actual && |
| 427 | test_grep ! "^--- a/" actual |
| 428 | ' |
| 429 | |
| 430 | test_expect_success '-L with --full-index' ' |
| 431 | git log -L:func2:file.c --full-index --format= >actual && |
| 432 | test_grep "^index $head_blob_old_full\.\.$head_blob_new_full 100644" actual && |
| 433 | test_grep "^index $null_blob_full\.\.$root_blob_full$" actual |
| 434 | ' |
| 435 | |
| 436 | test_expect_success 'setup -L with whitespace change' ' |
| 437 | git checkout -b ws-change parent-oids && |
| 438 | sed "s/ return F2 + 2;/ return F2 + 2;/" file.c >tmp && |
| 439 | mv tmp file.c && |
| 440 | git commit -a -m "Whitespace change in func2()" |
| 441 | ' |
| 442 | |
| 443 | test_expect_success '-L with --ignore-all-space suppresses whitespace-only diff' ' |
| 444 | git log -L:func2:file.c --format= >without_w && |
| 445 | git log -L:func2:file.c --format= -w >with_w && |
| 446 | |
| 447 | # Without -w: three commits produce diffs (whitespace, modify, root) |
| 448 | test $(grep -c "^diff --git" without_w) = 3 && |
| 449 | |
| 450 | # With -w: whitespace-only commit produces no hunk, so only two diffs |
| 451 | test $(grep -c "^diff --git" with_w) = 2 |
| 452 | ' |
| 453 | |
| 454 | test_expect_success 'show line-log with graph' ' |
| 455 | git checkout parent-oids && |
| 456 | head_blob_old=$(git rev-parse --short HEAD^:file.c) && |
| 457 | head_blob_new=$(git rev-parse --short HEAD:file.c) && |
| 458 | root_blob=$(git rev-parse --short HEAD~4:file.c) && |
| 459 | null_blob=$(test_oid zero | cut -c1-7) && |
| 460 | qz_to_tab_space >expect <<-EOF && |
| 461 | * $head_oid Modify func2() in file.c |
| 462 | | diff --git a/file.c b/file.c |
| 463 | | index $head_blob_old..$head_blob_new 100644 |
| 464 | | --- a/file.c |
| 465 | | +++ b/file.c |
| 466 | | @@ -6,4 +6,4 @@ int func1() |
| 467 | | int func2() |
| 468 | | { |
| 469 | | - return F2; |
| 470 | | + return F2 + 2; |
| 471 | | } |
| 472 | * $root_oid Add func1() and func2() in file.c |
| 473 | diff --git a/file.c b/file.c |
| 474 | new file mode 100644 |
| 475 | index $null_blob..$root_blob |
| 476 | --- /dev/null |
| 477 | +++ b/file.c |
| 478 | @@ -0,0 +6,4 @@ |
| 479 | +int func2() |
| 480 | +{ |
| 481 | + return F2; |
| 482 | +} |
| 483 | EOF |
| 484 | git log --graph --oneline -L:func2:file.c >actual && |
| 485 | test_cmp expect actual |
| 486 | ' |
| 487 | |
| 488 | test_expect_success 'setup for -L with -G/-S/--find-object and a merge with rename' ' |
| 489 | git checkout --orphan pickaxe-rename && |
| 490 | git reset --hard && |
| 491 | |
| 492 | echo content >file && |
| 493 | git add file && |
| 494 | git commit -m "add file" && |
| 495 | |
| 496 | git checkout -b pickaxe-rename-side && |
| 497 | git mv file renamed-file && |
| 498 | git commit -m "rename file" && |
| 499 | |
| 500 | git checkout pickaxe-rename && |
| 501 | git commit --allow-empty -m "diverge" && |
| 502 | git merge --no-edit pickaxe-rename-side && |
| 503 | |
| 504 | git mv renamed-file file && |
| 505 | git commit -m "rename back" |
| 506 | ' |
| 507 | |
| 508 | test_expect_success '-L -G does not crash with merge and rename' ' |
| 509 | git log --format="%s" --no-patch -L 1,1:file -G "." >actual |
| 510 | ' |
| 511 | |
| 512 | test_expect_success '-L -S does not crash with merge and rename' ' |
| 513 | git log --format="%s" --no-patch -L 1,1:file -S content >actual |
| 514 | ' |
| 515 | |
| 516 | test_expect_success '-L --find-object does not crash with merge and rename' ' |
| 517 | git log --format="%s" --no-patch -L 1,1:file \ |
| 518 | --find-object=$(git rev-parse HEAD:file) >actual |
| 519 | ' |
| 520 | |
| 521 | test_expect_success '-L -G should filter commits by pattern' ' |
| 522 | git log --format="%s" --no-patch -L 1,1:file -G "nomatch" >actual && |
| 523 | test_must_be_empty actual |
| 524 | ' |
| 525 | |
| 526 | test_expect_success '-L -S should filter commits by pattern' ' |
| 527 | git log --format="%s" --no-patch -L 1,1:file -S "nomatch" >actual && |
| 528 | test_must_be_empty actual |
| 529 | ' |
| 530 | |
| 531 | test_expect_success '-L --find-object should filter commits by object' ' |
| 532 | git log --format="%s" --no-patch -L 1,1:file \ |
| 533 | --find-object=$ZERO_OID >actual && |
| 534 | test_must_be_empty actual |
| 535 | ' |
| 536 | |
| 537 | test_expect_success '-L with --word-diff-regex' ' |
| 538 | git checkout parent-oids && |
| 539 | git log -L:func2:file.c --word-diff \ |
| 540 | --word-diff-regex="[a-zA-Z0-9_]+" --format= >actual && |
| 541 | # Word-diff markers must be present |
| 542 | test_grep "{+" actual && |
| 543 | test_grep "+}" actual && |
| 544 | # No line-level +/- markers (word-diff replaces them); |
| 545 | # exclude --- header lines from the check |
| 546 | test_grep ! "^+[^+]" actual && |
| 547 | test_grep ! "^-[^-]" actual |
| 548 | ' |
| 549 | |
| 550 | test_expect_success '-L with --src-prefix and --dst-prefix' ' |
| 551 | git checkout parent-oids && |
| 552 | git log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \ |
| 553 | --format= >actual && |
| 554 | test_grep "^diff --git old/file.c new/file.c" actual && |
| 555 | test_grep "^--- old/file.c" actual && |
| 556 | test_grep "^+++ new/file.c" actual && |
| 557 | test_grep ! "^--- a/" actual |
| 558 | ' |
| 559 | |
| 560 | test_expect_success '-L with --abbrev' ' |
| 561 | git checkout parent-oids && |
| 562 | git log -L:func2:file.c --abbrev=4 --format= -1 >actual && |
| 563 | # 4-char abbreviated hashes on index line |
| 564 | test_grep "^index [0-9a-f]\{4\}\.\.[0-9a-f]\{4\}" actual |
| 565 | ' |
| 566 | |
| 567 | test_expect_success '-L with -b suppresses whitespace-only diff' ' |
| 568 | git checkout ws-change && |
| 569 | git log -L:func2:file.c --format= >without_b && |
| 570 | git log -L:func2:file.c --format= -b >with_b && |
| 571 | test $(grep -c "^diff --git" without_b) = 3 && |
| 572 | test $(grep -c "^diff --git" with_b) = 2 |
| 573 | ' |
| 574 | |
| 575 | test_expect_success '-L with --output-indicator-*' ' |
| 576 | git checkout parent-oids && |
| 577 | git log -L:func2:file.c --output-indicator-new=">" \ |
| 578 | --output-indicator-old="<" --output-indicator-context="|" \ |
| 579 | --format= -1 >actual && |
| 580 | test_grep "^>" actual && |
| 581 | test_grep "^<" actual && |
| 582 | test_grep "^|" actual && |
| 583 | # No standard +/-/space content markers; exclude ---/+++ headers |
| 584 | test_grep ! "^+[^+]" actual && |
| 585 | test_grep ! "^-[^-]" actual && |
| 586 | test_grep ! "^ " actual |
| 587 | ' |
| 588 | |
| 589 | test_expect_success '-L with -R reverses diff' ' |
| 590 | git checkout parent-oids && |
| 591 | git log -L:func2:file.c -R --format= -1 >actual && |
| 592 | test_grep "^diff --git b/file.c a/file.c" actual && |
| 593 | test_grep "^--- b/file.c" actual && |
| 594 | test_grep "^+++ a/file.c" actual && |
| 595 | # The modification added "F2 + 2", so reversed it is removed |
| 596 | test_grep "^-.*F2 + 2" actual && |
| 597 | test_grep "^+.*return F2;" actual |
| 598 | ' |
| 599 | |
| 600 | test_expect_success 'setup for color-moved test' ' |
| 601 | git checkout -b color-moved-test parent-oids && |
| 602 | cat >big.c <<-\EOF && |
| 603 | int bigfunc() |
| 604 | { |
| 605 | int a = 1; |
| 606 | int b = 2; |
| 607 | int c = 3; |
| 608 | return a + b + c; |
| 609 | } |
| 610 | EOF |
| 611 | git add big.c && |
| 612 | git commit -m "add bigfunc" && |
| 613 | sed "s/ / /" big.c >tmp && mv tmp big.c && |
| 614 | git commit -a -m "reindent bigfunc" |
| 615 | ' |
| 616 | |
| 617 | test_expect_success '-L with --color-moved' ' |
| 618 | git log -L:bigfunc:big.c --color-moved=zebra \ |
| 619 | --color-moved-ws=ignore-all-space \ |
| 620 | --color=always --format= -1 >actual.raw && |
| 621 | test_decode_color <actual.raw >actual && |
| 622 | # Old moved lines: bold magenta; new moved lines: bold cyan |
| 623 | test_grep "BOLD;MAGENTA" actual && |
| 624 | test_grep "BOLD;CYAN" actual |
| 625 | ' |
| 626 | |
| 627 | test_expect_success 'setup for no-newline-at-eof tests' ' |
| 628 | git checkout --orphan no-newline && |
| 629 | git reset --hard && |
| 630 | printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 2;\n}" >noeol.c && |
| 631 | git add noeol.c && |
| 632 | test_tick && |
| 633 | git commit -m "add noeol.c (no trailing newline)" && |
| 634 | sed "s/return 2/return 22/" noeol.c >tmp && mv tmp noeol.c && |
| 635 | git commit -a -m "modify bot()" && |
| 636 | printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 33;\n}\n" >noeol.c && |
| 637 | git commit -a -m "modify bot() and add trailing newline" |
| 638 | ' |
| 639 | |
| 640 | # When the tracked function is at the end of a file with no trailing |
| 641 | # newline, the "\ No newline at end of file" marker should appear. |
| 642 | test_expect_success '-L no-newline-at-eof appears in tracked range' ' |
| 643 | git log -L:bot:noeol.c --format= -1 HEAD~1 >actual && |
| 644 | test_grep "No newline at end of file" actual |
| 645 | ' |
| 646 | |
| 647 | # When tracking a function that ends before the no-newline content, |
| 648 | # the marker should not appear in the output. |
| 649 | test_expect_success '-L no-newline-at-eof suppressed outside range' ' |
| 650 | git log -L:top:noeol.c --format= >actual && |
| 651 | test_grep ! "No newline at end of file" actual |
| 652 | ' |
| 653 | |
| 654 | # When a commit removes a no-newline last line and replaces it with |
| 655 | # a newline-terminated line, the marker should still appear (on the |
| 656 | # old side of the diff). |
| 657 | test_expect_success '-L no-newline-at-eof marker with deleted line' ' |
| 658 | git log -L:bot:noeol.c --format= -1 >actual && |
| 659 | test_grep "No newline at end of file" actual |
| 660 | ' |
| 661 | |
| 662 | test_expect_success 'setup for range boundary deletion test' ' |
| 663 | git checkout --orphan range-boundary && |
| 664 | git reset --hard && |
| 665 | cat >boundary.c <<-\EOF && |
| 666 | void above() |
| 667 | { |
| 668 | return; |
| 669 | } |
| 670 | |
| 671 | void tracked() |
| 672 | { |
| 673 | int x = 1; |
| 674 | int y = 2; |
| 675 | } |
| 676 | |
| 677 | void below() |
| 678 | { |
| 679 | return; |
| 680 | } |
| 681 | EOF |
| 682 | git add boundary.c && |
| 683 | test_tick && |
| 684 | git commit -m "add boundary.c" && |
| 685 | cat >boundary.c <<-\EOF && |
| 686 | void above() |
| 687 | { |
| 688 | return; |
| 689 | } |
| 690 | |
| 691 | void tracked() |
| 692 | { |
| 693 | int x = 1; |
| 694 | int y = 2; |
| 695 | } |
| 696 | |
| 697 | void below_renamed() |
| 698 | { |
| 699 | return 0; |
| 700 | } |
| 701 | EOF |
| 702 | git commit -a -m "modify below() only" |
| 703 | ' |
| 704 | |
| 705 | # When only a function below the tracked range is modified, the |
| 706 | # tracked function should not produce a diff. |
| 707 | test_expect_success '-L suppresses deletions outside tracked range' ' |
| 708 | git log -L:tracked:boundary.c --format= >actual && |
| 709 | test $(grep -c "^diff --git" actual) = 1 |
| 710 | ' |
| 711 | |
| 712 | test_expect_success '-L with -S filters to string-count changes' ' |
| 713 | git checkout parent-oids && |
| 714 | git log -L:func2:file.c -S "F2 + 2" --format= >actual && |
| 715 | # -S searches the whole file, not just the tracked range; |
| 716 | # combined with the -L range walk, this selects commits that |
| 717 | # both touch func2 and change the count of "F2 + 2" in the file. |
| 718 | test $(grep -c "^diff --git" actual) = 1 && |
| 719 | test_grep "F2 + 2" actual |
| 720 | ' |
| 721 | |
| 722 | test_expect_success '-L with -G filters to diff-text matches' ' |
| 723 | git checkout parent-oids && |
| 724 | git log -L:func2:file.c -G "F2 [+] 2" --format= >actual && |
| 725 | # -G greps the diff text, and under -L only the lines in the |
| 726 | # tracked range (unlike -S above, which searches the whole file); |
| 727 | # this selects commits whose change to func2 contains "F2 + 2". |
| 728 | test $(grep -c "^diff --git" actual) = 1 && |
| 729 | test_grep "F2 + 2" actual |
| 730 | ' |
| 731 | |
| 732 | test_expect_success 'setup for trailing deletion test' ' |
| 733 | git checkout --orphan trailing-del && |
| 734 | git reset --hard && |
| 735 | cat >file.c <<-\EOF && |
| 736 | void tracked() |
| 737 | { |
| 738 | return 1; |
| 739 | } |
| 740 | // trailing comment |
| 741 | EOF |
| 742 | git add file.c && |
| 743 | test_tick && |
| 744 | git commit -m "add file with trailing comment" && |
| 745 | # Modify tracked() AND delete the trailing comment in |
| 746 | # one commit, so the commit touches the tracked range |
| 747 | # and is not filtered out by the revision walker. |
| 748 | cat >file.c <<-\EOF && |
| 749 | void tracked() |
| 750 | { |
| 751 | return 2; |
| 752 | } |
| 753 | EOF |
| 754 | git commit -a -m "modify tracked and delete trailing comment" |
| 755 | ' |
| 756 | |
| 757 | test_expect_success '-L does not include deletions past end of tracked range' ' |
| 758 | git log -L:tracked:file.c --format= -1 -p >actual && |
| 759 | # The trailing comment deletion is outside the tracked |
| 760 | # range and should not appear in the patch output. |
| 761 | test_grep "return 2" actual && |
| 762 | test_grep ! "trailing comment" actual |
| 763 | ' |
| 764 | |
| 765 | test_expect_success '-L includes leading deletions resolved by in-range line' ' |
| 766 | git checkout --orphan leading-del && |
| 767 | git reset --hard && |
| 768 | cat >file.c <<-\EOF && |
| 769 | // leading comment |
| 770 | void tracked() |
| 771 | { |
| 772 | return 1; |
| 773 | } |
| 774 | EOF |
| 775 | git add file.c && |
| 776 | test_tick && |
| 777 | git commit -m "add file with leading comment" && |
| 778 | cat >file.c <<-\EOF && |
| 779 | void tracked() |
| 780 | { |
| 781 | return 2; |
| 782 | } |
| 783 | EOF |
| 784 | git commit -a -m "modify tracked and delete leading comment" && |
| 785 | git log -L:tracked:file.c --format= -1 -p >actual && |
| 786 | # The leading comment deletion is resolved by the next |
| 787 | # non-removal line (void tracked), which is in range: a |
| 788 | # removal is classified by the position of the following |
| 789 | # line, so it joins the range that line falls in. |
| 790 | test_grep "return 2" actual && |
| 791 | test_grep "leading comment" actual |
| 792 | ' |
| 793 | |
| 794 | test_expect_success 'setup for line-range filter edge cases' ' |
| 795 | git checkout --orphan filter-edge && |
| 796 | git reset --hard && |
| 797 | cat >file.c <<-\EOF && |
| 798 | void before() |
| 799 | { |
| 800 | return 0; |
| 801 | } |
| 802 | |
| 803 | void tracked() |
| 804 | { |
| 805 | int a = 1; |
| 806 | int b = 2; |
| 807 | int c = 3; |
| 808 | return a + b + c; |
| 809 | } |
| 810 | |
| 811 | void after() |
| 812 | { |
| 813 | return 9; |
| 814 | } |
| 815 | EOF |
| 816 | git add file.c && |
| 817 | test_tick && |
| 818 | git commit -m "initial" |
| 819 | ' |
| 820 | |
| 821 | test_expect_success '-L change at exact first line of range' ' |
| 822 | git checkout filter-edge && |
| 823 | # Change the function signature (first line of range) |
| 824 | sed "s/void tracked/int tracked/" file.c >tmp && |
| 825 | mv tmp file.c && |
| 826 | git commit -a -m "change first line" && |
| 827 | git log -L:tracked:file.c -p --format=%s -1 >actual && |
| 828 | test_grep "change first line" actual && |
| 829 | test_grep "+int tracked" actual && |
| 830 | test_grep "\\-void tracked" actual |
| 831 | ' |
| 832 | |
| 833 | test_expect_success '-L change at exact last line of range' ' |
| 834 | git checkout filter-edge && |
| 835 | git reset --hard HEAD~1 && |
| 836 | # Change the closing brace line (last line of range) |
| 837 | sed "s/^}$/} \/\/ end tracked/" file.c >tmp && |
| 838 | mv tmp file.c && |
| 839 | git commit -a -m "change last line" && |
| 840 | git log -L:tracked:file.c -p --format=%s -1 >actual && |
| 841 | test_grep "change last line" actual && |
| 842 | test_grep "end tracked" actual |
| 843 | ' |
| 844 | |
| 845 | test_expect_success '-L pure deletion in range (no additions)' ' |
| 846 | git checkout filter-edge && |
| 847 | git reset --hard HEAD~1 && |
| 848 | # Delete a line inside tracked() without adding anything |
| 849 | sed "/int c/d" file.c >tmp && |
| 850 | mv tmp file.c && |
| 851 | git commit -a -m "pure deletion" && |
| 852 | git log -L:tracked:file.c -p --format=%s -1 >actual && |
| 853 | test_grep "pure deletion" actual && |
| 854 | test_grep "\\-.*int c" actual |
| 855 | ' |
| 856 | |
| 857 | test_expect_success '-L with --diff-filter=M excludes root commit' ' |
| 858 | git checkout parent-oids && |
| 859 | git log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual && |
| 860 | # Root commit is an Add (A), not a Modify (M), so it should |
| 861 | # be excluded; only the modification commit remains. |
| 862 | echo "Modify func2() in file.c" >expect && |
| 863 | test_cmp expect actual |
| 864 | ' |
| 865 | |
| 866 | test_expect_success '-L with --diff-filter=A shows only root commit' ' |
| 867 | git checkout parent-oids && |
| 868 | git log -L:func2:file.c --diff-filter=A --format=%s --no-patch >actual && |
| 869 | echo "Add func1() and func2() in file.c" >expect && |
| 870 | test_cmp expect actual |
| 871 | ' |
| 872 | |
| 873 | test_expect_success '-L with -S suppresses non-matching commits' ' |
| 874 | git checkout parent-oids && |
| 875 | git log -L:func2:file.c -S "F2 + 2" --format=%s --no-patch >actual && |
| 876 | # Only the commit that changes the count of "F2 + 2" should appear. |
| 877 | echo "Modify func2() in file.c" >expect && |
| 878 | test_cmp expect actual |
| 879 | ' |
| 880 | |
| 881 | test_expect_success '--full-diff is not supported with -L' ' |
| 882 | test_must_fail git log -L1,24:b.c --full-diff 2>err && |
| 883 | test_grep "does not support" err |
| 884 | ' |
| 885 | |
| 886 | test_expect_success '-L --oneline has no extra blank line before diff' ' |
| 887 | git checkout parent-oids && |
| 888 | git log --oneline -L:func2:file.c -1 >actual && |
| 889 | # Oneline header on line 1, diff starts immediately on line 2 |
| 890 | sed -n 2p actual >line2 && |
| 891 | test_grep "^diff --git" line2 |
| 892 | ' |
| 893 | |
| 894 | test_expect_success 'setup for stat range-scoping tests' ' |
| 895 | git checkout --orphan stat-scoping && |
| 896 | git reset --hard && |
| 897 | cat >file.c <<-\EOF && |
| 898 | int func1() |
| 899 | { |
| 900 | return F1; |
| 901 | } |
| 902 | |
| 903 | int func2() |
| 904 | { |
| 905 | return F2; |
| 906 | } |
| 907 | EOF |
| 908 | git add file.c && |
| 909 | test_tick && |
| 910 | git commit -m "Add func1() and func2()" && |
| 911 | |
| 912 | # Modify both functions in a single commit so that |
| 913 | # whole-file stats differ from the counts for the tracked range. |
| 914 | sed -e "s/F1/F1 + 1/" -e "s/F2/F2 + 2/" file.c >tmp && |
| 915 | mv tmp file.c && |
| 916 | git commit -a -m "Modify both functions" |
| 917 | ' |
| 918 | |
| 919 | test_expect_success '--numstat counts only lines in tracked range' ' |
| 920 | # "Modify both functions" changes one line in func1 and one in |
| 921 | # func2. Whole-file numstat would show 2 added, 2 deleted. |
| 922 | # numstat for func2 within the tracked range should show only 1 and 1. |
| 923 | git log -L:func2:file.c --numstat --format=%s -1 >actual && |
| 924 | test_grep "Modify both functions" actual && |
| 925 | test_grep "^1 1 file.c$" actual && |
| 926 | test_grep ! "^diff --git" actual |
| 927 | ' |
| 928 | |
| 929 | test_expect_success '--numstat counts only additions for root commit' ' |
| 930 | # Root commit creates both func1 (4 lines) and func2 (4 lines). |
| 931 | # Whole-file numstat would show 9 lines added. numstat for func2 |
| 932 | # within the tracked range should show only 4. |
| 933 | git log -L:func2:file.c --numstat --format=%s >actual && |
| 934 | test_grep "Add func1() and func2()" actual && |
| 935 | test_grep "^4 0 file.c$" actual && |
| 936 | test_grep ! "^diff --git" actual |
| 937 | ' |
| 938 | |
| 939 | test_expect_success '--stat counts only lines in tracked range' ' |
| 940 | git log -L:func2:file.c --stat --format=%s -1 >actual && |
| 941 | test_grep "Modify both functions" actual && |
| 942 | test_grep "file.c |" actual && |
| 943 | test_grep "1 insertion" actual && |
| 944 | test_grep "1 deletion" actual && |
| 945 | test_grep ! "^diff --git" actual |
| 946 | ' |
| 947 | |
| 948 | test_expect_success '--shortstat counts only lines in tracked range' ' |
| 949 | # --shortstat prints only the summary line: no per-file "file.c |" |
| 950 | # line. Counts cover only the tracked range, as for --numstat above. |
| 951 | git log -L:func2:file.c --shortstat --format=%s -1 >actual && |
| 952 | test_grep "Modify both functions" actual && |
| 953 | test_grep "1 insertion" actual && |
| 954 | test_grep "1 deletion" actual && |
| 955 | test_grep ! "file.c |" actual && |
| 956 | test_grep ! "^diff --git" actual |
| 957 | ' |
| 958 | |
| 959 | test_expect_success '--numstat across renames and multiple commits' ' |
| 960 | # parallel-change carries the tracked function f across an a.c -> b.c |
| 961 | # rename and a merge of two parallel histories. With -M, --numstat |
| 962 | # follows the rename and reports added/removed counts for f within |
| 963 | # the tracked range (not whole-file) per commit; the file column flips from |
| 964 | # b.c to a.c at the rename as the walk goes back in time. Commits |
| 965 | # that do not change the range of f emit no row (the merge and the |
| 966 | # pure file-move produce nothing), so there are fewer rows than |
| 967 | # commits. |
| 968 | git checkout parallel-change && |
| 969 | git log -M -L ":f:b.c" --format= --numstat >actual && |
| 970 | cat >expect <<-\EOF && |
| 971 | 1 1 b.c |
| 972 | 1 1 a.c |
| 973 | 1 1 a.c |
| 974 | 1 1 a.c |
| 975 | 1 0 a.c |
| 976 | 13 0 a.c |
| 977 | EOF |
| 978 | test_cmp expect actual |
| 979 | ' |
| 980 | |
| 981 | test_expect_success '-L multiple ranges with --numstat excludes untracked change' ' |
| 982 | git checkout --orphan multi-range && |
| 983 | git reset --hard && |
| 984 | cat >m.c <<-\EOF && |
| 985 | int func1() |
| 986 | { |
| 987 | return F1; |
| 988 | } |
| 989 | |
| 990 | int func2() |
| 991 | { |
| 992 | return F2; |
| 993 | } |
| 994 | |
| 995 | int func3() |
| 996 | { |
| 997 | return F3; |
| 998 | } |
| 999 | EOF |
| 1000 | git add m.c && |
| 1001 | test_tick && |
| 1002 | git commit -m "add m.c" && |
| 1003 | # Change all three functions but track only func1 and func2. |
| 1004 | # Whole-file numstat would be 3 3; a 2 2 result proves the |
| 1005 | # untracked func3 change is excluded and the two ranges just sum. |
| 1006 | sed -e "s/F1/F1 + 1/" -e "s/F2/F2 + 2/" -e "s/F3/F3 + 3/" m.c >tmp && |
| 1007 | mv tmp m.c && |
| 1008 | git commit -a -m "Modify all three functions" && |
| 1009 | git log -L:func1:m.c -L:func2:m.c --numstat --format=%s -1 >actual && |
| 1010 | test_grep "Modify all three functions" actual && |
| 1011 | test_grep "^2 2 m.c$" actual && |
| 1012 | test_grep ! "^3 3 m.c$" actual |
| 1013 | ' |
| 1014 | |
| 1015 | test_expect_success '--summary shows new file on root commit' ' |
| 1016 | git checkout parent-oids && |
| 1017 | git log -L:func2:file.c --summary --format= >actual && |
| 1018 | test_grep "create mode 100644 file.c" actual |
| 1019 | ' |
| 1020 | |
| 1021 | test_expect_success 'setup for --check test' ' |
| 1022 | git checkout --orphan check-test && |
| 1023 | git reset --hard && |
| 1024 | cat >check.c <<-\EOF && |
| 1025 | void tracked() |
| 1026 | { |
| 1027 | return; |
| 1028 | } |
| 1029 | |
| 1030 | void other() |
| 1031 | { |
| 1032 | return; |
| 1033 | } |
| 1034 | EOF |
| 1035 | git add check.c && |
| 1036 | test_tick && |
| 1037 | git commit -m "add check.c" && |
| 1038 | # Introduce trailing whitespace errors in both functions |
| 1039 | sed "s/return;/return; /" check.c >check.c.tmp && |
| 1040 | mv check.c.tmp check.c && |
| 1041 | git commit -a -m "introduce trailing whitespace" |
| 1042 | ' |
| 1043 | |
| 1044 | test_expect_success '--check scoped to tracked range with correct file line' ' |
| 1045 | # tracked() trailing whitespace is at check.c:3; report it with the |
| 1046 | # real file line number, not a count from the start of the range |
| 1047 | # hunk. other() at check.c:8 is outside the range and is excluded. |
| 1048 | test_must_fail git log -L:tracked:check.c --check --format= >actual && |
| 1049 | test_grep "check.c:3: trailing whitespace" actual && |
| 1050 | test_grep ! "check.c:8:" actual |
| 1051 | ' |
| 1052 | |
| 1053 | test_expect_success '--check reports each of several tracked ranges' ' |
| 1054 | # Track both functions as separate ranges. Each range is flushed |
| 1055 | # as its own hunk, so the second error must report its real file |
| 1056 | # line (check.c:8), not continue the numbering from the first |
| 1057 | # range (check.c:3). |
| 1058 | test_must_fail git log -L:tracked:check.c -L:other:check.c \ |
| 1059 | --check --format= >actual && |
| 1060 | test_grep "check.c:3: trailing whitespace" actual && |
| 1061 | test_grep "check.c:8: trailing whitespace" actual |
| 1062 | ' |
| 1063 | |
| 1064 | test_expect_success '--check line numbers stay correct across a gap in one range' ' |
| 1065 | git checkout --orphan check-gap && |
| 1066 | git reset --hard && |
| 1067 | cat >gap.c <<-\EOF && |
| 1068 | void tracked() |
| 1069 | { |
| 1070 | int a = 1; |
| 1071 | int b = 2; |
| 1072 | int c = 3; |
| 1073 | int d = 4; |
| 1074 | int e = 5; |
| 1075 | int g = 7; |
| 1076 | return; |
| 1077 | } |
| 1078 | EOF |
| 1079 | git add gap.c && |
| 1080 | test_tick && |
| 1081 | git commit -m "add gap.c" && |
| 1082 | # Two trailing-whitespace errors within one tracked range, |
| 1083 | # separated by clean lines. ctxlen is inflated to the range span, |
| 1084 | # so they land in a single xdiff hunk with the gap as context; |
| 1085 | # both must report their real file line number, with the context |
| 1086 | # lines between them counted. |
| 1087 | sed -e "s/int a = 1;/int a = 1; /" -e "s/int g = 7;/int g = 7; /" gap.c >tmp && |
| 1088 | mv tmp gap.c && |
| 1089 | git commit -a -m "ws errors with a gap" && |
| 1090 | test_must_fail git log -L:tracked:gap.c --check --format= >actual && |
| 1091 | test_grep "gap.c:3: trailing whitespace" actual && |
| 1092 | test_grep "gap.c:8: trailing whitespace" actual |
| 1093 | ' |
| 1094 | |
| 1095 | test_expect_success '--check does not report blank-at-eof outside the range' ' |
| 1096 | git checkout --orphan check-eof && |
| 1097 | git reset --hard && |
| 1098 | printf "void tracked()\n{\n return;\n}\n\nint tail = 1;\n" >eof.c && |
| 1099 | git add eof.c && |
| 1100 | test_tick && |
| 1101 | git commit -m "add eof.c" && |
| 1102 | # One commit introduces a trailing-whitespace error inside tracked() |
| 1103 | # (line 3) and a blank line at end of file (line 7, outside the |
| 1104 | # range). The blank-at-eof check scans the whole file, so it must be |
| 1105 | # scoped: report the in-range error, not the out-of-range EOF blank. |
| 1106 | printf "void tracked()\n{\n return; \n}\n\nint tail = 1;\n\n" >eof.c && |
| 1107 | git commit -a -m "ws in range, blank at eof out of range" && |
| 1108 | test_must_fail git log -L:tracked:eof.c --check --format= >actual && |
| 1109 | test_grep "eof.c:3: trailing whitespace" actual && |
| 1110 | test_grep ! "blank line at EOF" actual |
| 1111 | ' |
| 1112 | |
| 1113 | test_expect_success '-L -G is scoped to the tracked range' ' |
| 1114 | git checkout --orphan grep-scope && |
| 1115 | git reset --hard && |
| 1116 | cat >gp.c <<-\EOF && |
| 1117 | int func1() |
| 1118 | { |
| 1119 | return ALPHA; |
| 1120 | } |
| 1121 | |
| 1122 | int func2() |
| 1123 | { |
| 1124 | return BETA; |
| 1125 | } |
| 1126 | EOF |
| 1127 | git add gp.c && |
| 1128 | test_tick && |
| 1129 | git commit -m "add gp.c" && |
| 1130 | sed -e "s/ALPHA/ALPHA2/" -e "s/BETA/BETA2/" gp.c >tmp && |
| 1131 | mv tmp gp.c && |
| 1132 | git commit -a -m "touch both functions" && |
| 1133 | # The commit changes ALPHA (func1) and BETA (func2). Tracking func2, |
| 1134 | # -G BETA matches its in-range change; -G ALPHA must not, since ALPHA |
| 1135 | # changes only outside the tracked range. |
| 1136 | git log -L:func2:gp.c -G BETA --format=%s >actual && |
| 1137 | test_grep "touch both functions" actual && |
| 1138 | git log -L:func2:gp.c -G ALPHA --format=%s >actual && |
| 1139 | test_grep ! "touch both functions" actual |
| 1140 | ' |
| 1141 | |
| 1142 | test_expect_success '-L -G searches the whole file under textconv' ' |
| 1143 | git checkout --orphan grep-textconv && |
| 1144 | git reset --hard && |
| 1145 | cat >tc.c <<-\EOF && |
| 1146 | int func1() |
| 1147 | { |
| 1148 | return F1; |
| 1149 | } |
| 1150 | |
| 1151 | int func2() |
| 1152 | { |
| 1153 | return F2; |
| 1154 | } |
| 1155 | EOF |
| 1156 | git add tc.c && |
| 1157 | test_tick && |
| 1158 | git commit -m "add tc.c" && |
| 1159 | # One commit changes func1 and func2; MAGIC lands only in the |
| 1160 | # func2 change, outside func1. |
| 1161 | sed -e "s/F1/F1 + 1/" -e "s/return F2/return MAGIC/" tc.c >tmp && |
| 1162 | mv tmp tc.c && |
| 1163 | git commit -a -m "change both funcs" && |
| 1164 | echo "tc.c diff=tc" >.gitattributes && |
| 1165 | |
| 1166 | # Without a textconv driver, -G is scoped to func1, so MAGIC (only |
| 1167 | # in the func2 change) does not select the commit. |
| 1168 | git log -L:func1:tc.c -G MAGIC --format=%s --no-patch >actual && |
| 1169 | test_must_be_empty actual && |
| 1170 | |
| 1171 | # A textconv driver makes the range (original-file line numbers) |
| 1172 | # meaningless against the driver output, so -G falls back to the |
| 1173 | # whole file and MAGIC now selects the commit. |
| 1174 | git config diff.tc.textconv cat && |
| 1175 | git log -L:func1:tc.c -G MAGIC --format=%s --no-patch >actual && |
| 1176 | test_grep "change both funcs" actual |
| 1177 | ' |
| 1178 | |
| 1179 | test_expect_success 'get_commit_action() does not mutate a not-yet-walked commit' ' |
| 1180 | git init peek && |
| 1181 | ( |
| 1182 | cd peek && |
| 1183 | test_write_lines 1 2 3 4 5 >f.c && |
| 1184 | git add f.c && test_tick && git commit -m base && |
| 1185 | test_write_lines 1 two 3 4 5 >f.c && |
| 1186 | test_tick && git commit -am change && |
| 1187 | |
| 1188 | # Peek HEAD^, which the walk has not reached (the out-of-order |
| 1189 | # call a lookahead makes), and confirm get_commit_action() leaves |
| 1190 | # it untouched. A side effect is invisible in the commit list |
| 1191 | # (range merges are idempotent), so the helper reports whether the |
| 1192 | # call mutated the peeked commit at all. |
| 1193 | echo "mutated 0" >expect && |
| 1194 | test-tool revision-walking line-log-peek HEAD^ 1,3:f.c HEAD >actual && |
| 1195 | test_cmp expect actual |
| 1196 | ) |
| 1197 | ' |
| 1198 | |
| 1199 | test_done |