Skip to content

Commit d9f5d24

Browse files
authored
Merge branch 'git:master' into master
2 parents 0891c66 + 010afd3 commit d9f5d24

14 files changed

Lines changed: 297 additions & 41 deletions

Documentation/RelNotes/2.56.0.adoc

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,22 @@ Performance, Internal Implementation, Development Support etc.
298298
the image upgrade.
299299
(merge 1a1579c42d jk/ci-static-analysis-image-bump later to maint).
300300
301+
* The alias tests in 't/t0014-alias.sh' have been updated to dynamically
302+
query the list of deprecated commands using 'git
303+
--list-cmds=deprecated' to avoid test failures when running with
304+
'WITH_BREAKING_CHANGES' in a build directory that contains stale
305+
executables of formerly deprecated commands.
306+
(merge bc57ecb915 jk/t0014-dynamic-deprecated-cmds later to maint).
307+
308+
* The code path that deals with relative paths in the diff-lib has
309+
been cleaned up.
310+
311+
* The get_commit_action() function has been refactored to be a pure
312+
predicate by moving the side-effecting line-level log range folding to
313+
simplify_commit(). This ensures that evaluating a commit's action
314+
before the walk reaches it does not prematurely mutate its tracked
315+
line ranges, making it safer for potential lookahead evaluations.
316+
301317
302318
Fixes since v2.55
303319
-----------------
@@ -496,14 +512,26 @@ Fixes since v2.55
496512
and another prevented the editor from opening when the final command
497513
in a chain containing 'fixup -c' was skipped.
498514

499-
* The alias tests in 't/t0014-alias.sh' have been updated to dynamically
500-
query the list of deprecated commands using 'git
501-
--list-cmds=deprecated' to avoid test failures when running with
502-
'WITH_BREAKING_CHANGES' in a build directory that contains stale
503-
executables of formerly deprecated commands.
504-
(merge bc57ecb915 jk/t0014-dynamic-deprecated-cmds later to maint).
505-
506515
* Git for Windows has been updated to avoid auto-detecting the symlink
507516
type if the target path starts with a slash, preventing NTLM
508517
credential leaks when checking out repositories with crafted
509518
symbolic links pointing to network shares.
519+
520+
* 'git cat-file --batch-command' that asked for 'contents' without
521+
'type' segfaults, which has been corrected.
522+
(merge 2abc7f0304 jk/cat-file-batch-wo-type-fix later to maint).
523+
524+
* A memory leak in 'git merge' when run without arguments (which
525+
triggers the default-to-upstream path) has been fixed. A test has
526+
been added to cover this case.
527+
(merge 68cce04a02 tc/merge-default-to-upstream-leakfix later to maint).
528+
529+
* A boundary case check in reachability bitmap traversal has been
530+
corrected to properly handle the object at position zero, which was
531+
previously skipped, leading to redundant bitmap loading.
532+
(merge b56b48301e dl/pack-bitmap-position-zero later to maint).
533+
534+
* A crash in the 'sparse-index' collapse code when encountering an
535+
invalidated cache-tree node (due to an intent-to-add path) has been
536+
fixed by avoiding collapsing such subtrees.
537+
(merge eede1e69fe ds/sparse-index-ita-crash later to maint).

builtin/cat-file.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,8 +789,11 @@ static void parse_cmd_contents(struct batch_options *opt,
789789
struct strbuf *output,
790790
struct expand_data *data)
791791
{
792+
enum object_type *saved_typep = data->info.typep;
793+
data->info.typep = &data->type;
792794
opt->batch_mode = BATCH_MODE_CONTENTS;
793795
batch_one_object(line, output, opt, data);
796+
data->info.typep = saved_typep;
794797
}
795798

796799
static void parse_cmd_info(struct batch_options *opt,

builtin/merge.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ int cmd_merge(int argc,
13731373
struct commit_list *common = NULL;
13741374
const char *best_strategy = NULL, *wt_strategy = NULL;
13751375
struct commit_list *remoteheads = NULL, *p;
1376-
void *branch_to_free;
1376+
void *branch_to_free, *argv_to_free = NULL;
13771377
int orig_argc = argc;
13781378
int merge_log_config = -1;
13791379

@@ -1517,8 +1517,10 @@ int cmd_merge(int argc,
15171517
option_commit = 1;
15181518

15191519
if (!argc) {
1520-
if (default_to_upstream)
1520+
if (default_to_upstream) {
15211521
argc = setup_with_upstream(&argv);
1522+
argv_to_free = argv;
1523+
}
15221524
else
15231525
die(_("No commit specified and merge.defaultToUpstream not set."));
15241526
} else if (argc == 1 && !strcmp(argv[0], "-")) {
@@ -1880,6 +1882,7 @@ int cmd_merge(int argc,
18801882
}
18811883
strbuf_release(&buf);
18821884
free(branch_to_free);
1885+
free(argv_to_free);
18831886
free(pull_twohead);
18841887
free(pull_octopus);
18851888
discard_index(the_repository->index);

diff-lib.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,11 +508,9 @@ static void do_oneway_diff(struct unpack_trees_options *o,
508508
* For diffing, the index is more important, and we only have a
509509
* single tree.
510510
*
511-
* We're supposed to advance o->pos to skip what we have already processed.
512-
*
513511
* This wrapper makes it all more readable, and takes care of all
514512
* the fairly complex unpack_trees() semantic requirements, including
515-
* the skipping, the path matching, the type conflict cases etc.
513+
* the path matching, the type conflict cases etc.
516514
*/
517515
static int oneway_diff(const struct cache_entry * const *src,
518516
struct unpack_trees_options *o)
@@ -540,6 +538,11 @@ static int oneway_diff(const struct cache_entry * const *src,
540538
if (!idx && !tree)
541539
BUG("oneway_diff with neither idx nor tree");
542540

541+
if (revs->diffopt.prefix &&
542+
strncmp((idx ? idx : tree)->name, revs->diffopt.prefix,
543+
revs->diffopt.prefix_length))
544+
return 0;
545+
543546
if (ce_path_match(revs->diffopt.repo->index,
544547
idx ? idx : tree,
545548
&revs->prune_data, NULL)) {

pack-bitmap.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1569,7 +1569,7 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
15691569

15701570
if (base) {
15711571
int pos = bitmap_position(bitmap_git, &object->oid);
1572-
if (pos > 0 && bitmap_get(base, pos)) {
1572+
if (pos >= 0 && bitmap_get(base, pos)) {
15731573
object->flags |= SEEN;
15741574
continue;
15751575
}

revision.c

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4196,37 +4196,39 @@ static timestamp_t comparison_date(const struct rev_info *revs,
41964196
commit->date;
41974197
}
41984198

4199-
enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit)
4199+
/*
4200+
* Whether the commit is ignored by the cheap checks that read only its
4201+
* traversal flags and pack membership (e.g. already shown, or marked
4202+
* uninteresting), before any check that examines the commit's date,
4203+
* parents, message, or diff.
4204+
*/
4205+
static int commit_early_ignore(struct rev_info *revs, struct commit *commit)
42004206
{
42014207
if (commit->object.flags & SHOWN)
4202-
return commit_ignore;
4208+
return 1;
42034209
if (revs->maximal_only && (commit->object.flags & CHILD_VISITED))
4204-
return commit_ignore;
4210+
return 1;
42054211
if (revs->unpacked && has_object_pack(revs->repo, &commit->object.oid))
4206-
return commit_ignore;
4207-
if (revs->no_kept_objects) {
4208-
if (has_object_kept_pack(revs->repo, &commit->object.oid,
4209-
revs->keep_pack_cache_flags))
4210-
return commit_ignore;
4211-
}
4212+
return 1;
4213+
if (revs->no_kept_objects &&
4214+
has_object_kept_pack(revs->repo, &commit->object.oid,
4215+
revs->keep_pack_cache_flags))
4216+
return 1;
42124217
if (commit->object.flags & UNINTERESTING)
4218+
return 1;
4219+
return 0;
4220+
}
4221+
4222+
/*
4223+
* Decide whether this commit is shown or ignored. Keep it a pure
4224+
* predicate: callers such as the commit graph depend on it having no
4225+
* side effects, so per-commit mutations (such as -L range tracking)
4226+
* belong in the caller, simplify_commit(), not here.
4227+
*/
4228+
enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit)
4229+
{
4230+
if (commit_early_ignore(revs, commit))
42134231
return commit_ignore;
4214-
if (revs->line_level_traverse && !want_ancestry(revs)) {
4215-
/*
4216-
* In case of line-level log with parent rewriting
4217-
* prepare_revision_walk() already took care of all line-level
4218-
* log filtering, and there is nothing left to do here.
4219-
*
4220-
* If parent rewriting was not requested, then this is the
4221-
* place to perform the line-level log filtering. Notably,
4222-
* this check, though expensive, must come before the other,
4223-
* cheaper filtering conditions, because the tracked line
4224-
* ranges must be adjusted even when the commit will end up
4225-
* being ignored based on other conditions.
4226-
*/
4227-
if (!line_log_process_ranges_arbitrary_commit(revs, commit))
4228-
return commit_ignore;
4229-
}
42304232
if (revs->min_age != -1 &&
42314233
comparison_date(revs, commit) > revs->min_age)
42324234
return commit_ignore;
@@ -4335,7 +4337,23 @@ struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit
43354337

43364338
enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
43374339
{
4338-
enum commit_action action = get_commit_action(revs, commit);
4340+
enum commit_action action;
4341+
4342+
/*
4343+
* For a line-level log without parent rewriting, fold each commit's
4344+
* ranges as the walk reaches it (parent rewriting does this eagerly in
4345+
* prepare_revision_walk()). Fold before get_commit_action() so the
4346+
* ranges carry across a commit that a later, cheaper check ignores;
4347+
* the commit_early_ignore() guard skips a commit get_commit_action()
4348+
* would ignore outright.
4349+
*/
4350+
if (revs->line_level_traverse && !want_ancestry(revs) &&
4351+
!commit_early_ignore(revs, commit)) {
4352+
if (!line_log_process_ranges_arbitrary_commit(revs, commit))
4353+
return commit_ignore;
4354+
}
4355+
4356+
action = get_commit_action(revs, commit);
43394357

43404358
if (action == commit_show &&
43414359
revs->prune && revs->dense && want_ancestry(revs)) {

sparse-index.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,17 @@ static int convert_to_sparse_rec(struct index_state *istate,
113113
continue;
114114
}
115115

116+
span = ct->down[pos]->cache_tree->entry_count;
117+
if (span < 0) {
118+
/* cache-tree entry is invalidated, cannot collapse. */
119+
istate->cache[num_converted++] = ce;
120+
i++;
121+
continue;
122+
}
123+
116124
strbuf_setlen(&child_path, 0);
117125
strbuf_add(&child_path, ce->name, slash - ce->name + 1);
118126

119-
span = ct->down[pos]->cache_tree->entry_count;
120127
count = convert_to_sparse_rec(istate,
121128
num_converted, i, i + span,
122129
child_path.buf, child_path.len,

t/helper/test-revision-walking.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
#include "test-tool.h"
1414
#include "commit.h"
1515
#include "diff.h"
16+
#include "line-log.h"
17+
#include "object-name.h"
1618
#include "repository.h"
1719
#include "revision.h"
1820
#include "setup.h"
21+
#include "string-list.h"
1922

2023
static void print_commit(struct commit *commit)
2124
{
@@ -51,6 +54,60 @@ static int run_revision_walk(void)
5154
return got_revision;
5255
}
5356

57+
/*
58+
* Check that get_commit_action() is a pure predicate by evaluating it on a
59+
* commit the walk has not reached yet. No git command makes that out-of-order
60+
* call, so this probe does it deliberately, and reports whether the call
61+
* mutated the peeked commit: a pure get_commit_action() leaves it untouched.
62+
* We compare the commit's flags rather than the emitted commit list because
63+
* range merges are idempotent, so a side effect would not change which commits
64+
* are shown. Only meaningful for a plain "-L" walk with no parent rewriting.
65+
*/
66+
static int line_log_peek(const char **argv)
67+
{
68+
struct repository *repo = the_repository;
69+
struct rev_info rev;
70+
struct string_list range_args = STRING_LIST_INIT_DUP;
71+
struct object_id oid;
72+
struct commit *peek;
73+
const char *rev_argv[3];
74+
unsigned before, after;
75+
76+
if (repo_get_oid(repo, argv[0], &oid))
77+
die("bad peek commit: %s", argv[0]);
78+
peek = lookup_commit_reference(repo, &oid);
79+
if (!peek || repo_parse_commit(repo, peek))
80+
die("cannot parse peek commit: %s", argv[0]);
81+
82+
repo_init_revisions(repo, &rev, NULL);
83+
rev.diffopt.flags.recursive = 1;
84+
rev.line_level_traverse = 1;
85+
string_list_append(&range_args, argv[1]);
86+
87+
rev_argv[0] = "line-log-peek";
88+
rev_argv[1] = argv[2];
89+
rev_argv[2] = NULL;
90+
setup_revisions(2, rev_argv, &rev, NULL);
91+
92+
line_log_init(&rev, NULL, &range_args);
93+
94+
if (rev.rewrite_parents || rev.children.name)
95+
die("line-log-peek requires a non-ancestry (-L, no --graph) walk");
96+
97+
if (prepare_revision_walk(&rev))
98+
die("prepare_revision_walk failed");
99+
100+
before = peek->object.flags;
101+
get_commit_action(&rev, peek);
102+
after = peek->object.flags;
103+
104+
printf("mutated %d\n", before != after);
105+
106+
release_revisions(&rev);
107+
string_list_clear(&range_args, 0);
108+
return 0;
109+
}
110+
54111
int cmd__revision_walking(int argc, const char **argv)
55112
{
56113
if (argc < 2)
@@ -69,6 +126,12 @@ int cmd__revision_walking(int argc, const char **argv)
69126
return 0;
70127
}
71128

129+
if (!strcmp(argv[1], "line-log-peek")) {
130+
if (argc != 5)
131+
die("usage: test-tool revision-walking line-log-peek <peek-commit> <start,end:file> <rev>");
132+
return line_log_peek(argv + 2);
133+
}
134+
72135
fprintf(stderr, "check usage\n");
73136
return 1;
74137
}

t/t1006-cat-file.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,14 @@ test_expect_success 'batch-command flush without --buffer' '
13401340
test_grep "^fatal:.*flush is only for --buffer mode.*" err
13411341
'
13421342

1343+
test_expect_success 'batch-command contents auto-handles type' '
1344+
echo "HEAD" |
1345+
git cat-file --batch="%(objectname)" >expect &&
1346+
echo "contents HEAD" |
1347+
git cat-file --batch-command="%(objectname)" >actual &&
1348+
test_cmp expect actual
1349+
'
1350+
13431351
perl_script='
13441352
use warnings;
13451353
use strict;

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,54 @@ test_expect_success 'add, commit, checkout' '
384384
test_all_match git checkout -
385385
'
386386

387+
test_expect_success 'intent-to-add entries outside sparse-checkout' '
388+
init_repos &&
389+
390+
write_script edit-contents <<-\EOF &&
391+
echo text >>$1
392+
EOF
393+
394+
test_sparse_match git sparse-checkout set deep folder1 &&
395+
run_on_sparse mkdir -p folder1 &&
396+
run_on_all ../edit-contents folder1/newita &&
397+
test_sparse_match git add -N folder1/newita &&
398+
399+
test_sparse_match git sparse-checkout set deep &&
400+
test_sparse_match git status --porcelain=v2 &&
401+
test_sparse_match git ls-files --stage
402+
'
403+
404+
test_expect_success 'intent-to-add with --sparse outside sparse-checkout' '
405+
init_repos &&
406+
407+
write_script edit-contents <<-\EOF &&
408+
echo text >>$1
409+
EOF
410+
411+
run_on_all mkdir -p folder1 &&
412+
run_on_all ../edit-contents folder1/newita &&
413+
test_all_match git add --sparse --intent-to-add folder1/newita &&
414+
415+
test_all_match git status --porcelain=v2 &&
416+
test_all_match git ls-files --stage &&
417+
test_all_match git diff --cached --stat &&
418+
419+
# Ensure sparse index stores correct sparse directories and
420+
# intent-to-add path.
421+
git -C sparse-index ls-files --format="%(path)" --sparse >out &&
422+
423+
# These paths should be present in index as-is.
424+
test_grep "^before/\$" out &&
425+
test_grep "^folder1/newita\$" out &&
426+
test_grep "^folder2/\$" out &&
427+
test_grep "^x/\$" out &&
428+
429+
# folder/0/ could theoretically be collapsed to a sparse
430+
# directory entry, but the current implementation avoids the
431+
# reduction because of folder1/newita
432+
test_grep "^folder1/0/0/0\$" out
433+
'
434+
387435
test_expect_success 'git add, checkout, and reset with -p' '
388436
init_repos &&
389437

0 commit comments

Comments
 (0)