log: "--since-as-filter" option is a non-terminating "--since" variant

The "--since=<time>" option of "git log" limits the commits displayed by the command by stopping the traversal once it sees a commit whose timestamp is older than the given time and not digging further into its parents. This is OK in a history where a commit always has a newer timestamp than any of its parents'. Once you see a commit older than the given <time>, all ancestor commits of it are even older than the time anyway. It poses, however, a problem when there is a commit with a wrong timestamp that makes it appear older than its parents. Stopping traversal at the "incorrectly old" commit will hide its ancestors that are newer than that wrong commit and are newer than the cut-off time given with the --since option. --max-age and --after being the synonyms to --since, they share the same issue. Add a new "--since-as-filter" option that is a variant of "--since=<time>". Instead of stopping the traversal to hide an old enough commit and its all ancestors, exclude commits with an old timestamp from the output but still keep digging the history. Without other traversal stopping options, this will force the command in "git log" family to dig down the history to the root. It may be an acceptable cost for a small project with short history and many commits with screwy timestamps. It is quite unlikely for us to add traversal stopper other than since, so have this as a --since-as-filter option, rather than a separate --as-filter, that would be probably more confusing. Signed-off-by: Miklos Vajna <vmiklos@vmiklos.hu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Miklos Vajna committed Apr 23, 2022 at 14:59 UTC 96697781e06e1b67aeee35a8798deb37e0b87ef4
4 files changed +57
Documentation/rev-list-options.txt
+5
@@ -25,6 +25,11 @@ ordering and formatting options, such as `--reverse`.
25 --after=<date>::
26 Show commits more recent than a specific date.
27
28 +--since-as-filter=<date>::
29 + Show all commits more recent than a specific date. This visits
30 + all commits in the range, rather than stopping at the first commit which
31 + is older than a specific date.
32 +
33 --until=<date>::
34 --before=<date>::
35 Show commits older than a specific date.
revision.c
+10
@@ -1440,6 +1440,9 @@ static int limit_list(struct rev_info *revs)
1440 if (revs->min_age != -1 && (commit->date > revs->min_age) &&
1441 !revs->line_level_traverse)
1442 continue;
1443 + if (revs->max_age_as_filter != -1 &&
1444 + (commit->date < revs->max_age_as_filter) && !revs->line_level_traverse)
1445 + continue;
1446 date = commit->date;
1447 p = &commit_list_insert(commit, p)->next;
1448
@@ -1838,6 +1841,7 @@ void repo_init_revisions(struct repository *r,
1841 revs->dense = 1;
1842 revs->prefix = prefix;
1843 revs->max_age = -1;
1844 + revs->max_age_as_filter = -1;
1845 revs->min_age = -1;
1846 revs->skip_count = -1;
1847 revs->max_count = -1;
@@ -2218,6 +2222,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
2222 } else if ((argcount = parse_long_opt("since", argv, &optarg))) {
2223 revs->max_age = approxidate(optarg);
2224 return argcount;
2225 + } else if ((argcount = parse_long_opt("since-as-filter", argv, &optarg))) {
2226 + revs->max_age_as_filter = approxidate(optarg);
2227 + return argcount;
2228 } else if ((argcount = parse_long_opt("after", argv, &optarg))) {
2229 revs->max_age = approxidate(optarg);
2230 return argcount;
@@ -3862,6 +3869,9 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
3869 if (revs->min_age != -1 &&
3870 comparison_date(revs, commit) > revs->min_age)
3871 return commit_ignore;
3872 + if (revs->max_age_as_filter != -1 &&
3873 + comparison_date(revs, commit) < revs->max_age_as_filter)
3874 + return commit_ignore;
3875 if (revs->min_parents || (revs->max_parents >= 0)) {
3876 int n = commit_list_count(commit->parents);
3877 if ((n < revs->min_parents) ||
revision.h
+1
@@ -263,6 +263,7 @@ struct rev_info {
263 int skip_count;
264 int max_count;
265 timestamp_t max_age;
266 + timestamp_t max_age_as_filter;
267 timestamp_t min_age;
268 int min_parents;
269 int max_parents;
t/t4217-log-limit.sh new
+41
@@ -0,0 +1,41 @@
1 +#!/bin/sh
2 +
3 +test_description='git log with filter options limiting the output'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup test' '
8 + git init &&
9 + echo a >file &&
10 + git add file &&
11 + GIT_COMMITTER_DATE="2021-02-01 00:00" git commit -m init &&
12 + echo a >>file &&
13 + git add file &&
14 + GIT_COMMITTER_DATE="2022-02-01 00:00" git commit -m first &&
15 + echo a >>file &&
16 + git add file &&
17 + GIT_COMMITTER_DATE="2021-03-01 00:00" git commit -m second &&
18 + echo a >>file &&
19 + git add file &&
20 + GIT_COMMITTER_DATE="2022-03-01 00:00" git commit -m third
21 +'
22 +
23 +test_expect_success 'git log --since-as-filter=...' '
24 + git log --since-as-filter="2022-01-01" --format=%s >actual &&
25 + cat >expect <<-\EOF &&
26 + third
27 + first
28 + EOF
29 + test_cmp expect actual
30 +'
31 +
32 +test_expect_success 'git log --children --since-as-filter=...' '
33 + git log --children --since-as-filter="2022-01-01" --format=%s >actual &&
34 + cat >expect <<-\EOF &&
35 + third
36 + first
37 + EOF
38 + test_cmp expect actual
39 +'
40 +
41 +test_done