Skip to content

Commit 9af3a7c

Browse files
committed
Merge branch 'ds/revision-show-pulls'
"git log" learned "--show-pulls" that helps pathspec limited history views; a merge commit that takes the whole change from a side branch, which is normally omitted from the output, is shown in addition to the commits that introduce real changes. * ds/revision-show-pulls: revision: --show-pulls adds helpful merges
2 parents 82fa169 + 8d049e1 commit 9af3a7c

File tree

5 files changed

+284
-5
lines changed

5 files changed

+284
-5
lines changed

Documentation/rev-list-options.txt

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,12 @@ Default mode::
342342
branches if the end result is the same (i.e. merging branches
343343
with the same content)
344344

345+
--show-pulls::
346+
Include all commits from the default mode, but also any merge
347+
commits that are not TREESAME to the first parent but are
348+
TREESAME to a later parent. This mode is helpful for showing
349+
the merge commits that "first introduced" a change to a branch.
350+
345351
--full-history::
346352
Same as the default mode, but does not prune some history.
347353

@@ -534,7 +540,7 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`:
534540
parent and is TREESAME.
535541
--
536542

537-
Finally, there is a fifth simplification mode available:
543+
There is another simplification mode available:
538544

539545
--ancestry-path::
540546
Limit the displayed commits to those directly on the ancestry
@@ -573,6 +579,132 @@ option does. Applied to the 'D..M' range, it results in:
573579
L--M
574580
-----------------------------------------------------------------------
575581

582+
Before discussing another option, `--show-pulls`, we need to
583+
create a new example history.
584+
+
585+
A common problem users face when looking at simplified history is that a
586+
commit they know changed a file somehow does not appear in the file's
587+
simplified history. Let's demonstrate a new example and show how options
588+
such as `--full-history` and `--simplify-merges` works in that case:
589+
+
590+
-----------------------------------------------------------------------
591+
.-A---M-----C--N---O---P
592+
/ / \ \ \/ / /
593+
I B \ R-'`-Z' /
594+
\ / \/ /
595+
\ / /\ /
596+
`---X--' `---Y--'
597+
-----------------------------------------------------------------------
598+
+
599+
For this example, suppose `I` created `file.txt` which was modified by
600+
`A`, `B`, and `X` in different ways. The single-parent commits `C`, `Z`,
601+
and `Y` do not change `file.txt`. The merge commit `M` was created by
602+
resolving the merge conflict to include both changes from `A` and `B`
603+
and hence is not TREESAME to either. The merge commit `R`, however, was
604+
created by ignoring the contents of `file.txt` at `M` and taking only
605+
the contents of `file.txt` at `X`. Hence, `R` is TREESAME to `X` but not
606+
`M`. Finally, the natural merge resolution to create `N` is to take the
607+
contents of `file.txt` at `R`, so `N` is TREESAME to `R` but not `C`.
608+
The merge commits `O` and `P` are TREESAME to their first parents, but
609+
not to their second parents, `Z` and `Y` respectively.
610+
+
611+
When using the default mode, `N` and `R` both have a TREESAME parent, so
612+
those edges are walked and the others are ignored. The resulting history
613+
graph is:
614+
+
615+
-----------------------------------------------------------------------
616+
I---X
617+
-----------------------------------------------------------------------
618+
+
619+
When using `--full-history`, Git walks every edge. This will discover
620+
the commits `A` and `B` and the merge `M`, but also will reveal the
621+
merge commits `O` and `P`. With parent rewriting, the resulting graph is:
622+
+
623+
-----------------------------------------------------------------------
624+
.-A---M--------N---O---P
625+
/ / \ \ \/ / /
626+
I B \ R-'`--' /
627+
\ / \/ /
628+
\ / /\ /
629+
`---X--' `------'
630+
-----------------------------------------------------------------------
631+
+
632+
Here, the merge commits `O` and `P` contribute extra noise, as they did
633+
not actually contribute a change to `file.txt`. They only merged a topic
634+
that was based on an older version of `file.txt`. This is a common
635+
issue in repositories using a workflow where many contributors work in
636+
parallel and merge their topic branches along a single trunk: manu
637+
unrelated merges appear in the `--full-history` results.
638+
+
639+
When using the `--simplify-merges` option, the commits `O` and `P`
640+
disappear from the results. This is because the rewritten second parents
641+
of `O` and `P` are reachable from their first parents. Those edges are
642+
removed and then the commits look like single-parent commits that are
643+
TREESAME to their parent. This also happens to the commit `N`, resulting
644+
in a history view as follows:
645+
+
646+
-----------------------------------------------------------------------
647+
.-A---M--.
648+
/ / \
649+
I B R
650+
\ / /
651+
\ / /
652+
`---X--'
653+
-----------------------------------------------------------------------
654+
+
655+
In this view, we see all of the important single-parent changes from
656+
`A`, `B`, and `X`. We also see the carefully-resolved merge `M` and the
657+
not-so-carefully-resolved merge `R`. This is usually enough information
658+
to determine why the commits `A` and `B` "disappeared" from history in
659+
the default view. However, there are a few issues with this approach.
660+
+
661+
The first issue is performance. Unlike any previous option, the
662+
`--simplify-merges` option requires walking the entire commit history
663+
before returning a single result. This can make the option difficult to
664+
use for very large repositories.
665+
+
666+
The second issue is one of auditing. When many contributors are working
667+
on the same repository, it is important which merge commits introduced
668+
a change into an important branch. The problematic merge `R` above is
669+
not likely to be the merge commit that was used to merge into an
670+
important branch. Instead, the merge `N` was used to merge `R` and `X`
671+
into the important branch. This commit may have information about why
672+
the change `X` came to override the changes from `A` and `B` in its
673+
commit message.
674+
+
675+
The `--show-pulls` option helps with both of these issues by adding more
676+
merge commits to the history results. If a merge is not TREESAME to its
677+
first parent but is TREESAME to a later parent, then that merge is
678+
treated as if it "pulled" the change from another branch. When using
679+
`--show-pulls` on this example (and no other options) the resulting
680+
graph is:
681+
+
682+
-----------------------------------------------------------------------
683+
I---X---R---N
684+
-----------------------------------------------------------------------
685+
+
686+
Here, the merge commits `R` and `N` are included because they pulled
687+
the commits `X` and `R` into the base branch, respectively. These
688+
merges are the reason the commits `A` and `B` do not appear in the
689+
default history.
690+
+
691+
When `--show-pulls` is paired with `--simplify-merges`, the
692+
graph includes all of the necessary information:
693+
+
694+
-----------------------------------------------------------------------
695+
.-A---M--. N
696+
/ / \ /
697+
I B R
698+
\ / /
699+
\ / /
700+
`---X--'
701+
-----------------------------------------------------------------------
702+
+
703+
Notice that since `M` is reachable from `R`, the edge from `N` to `M`
704+
was simplified away. However, `N` still appears in the history as an
705+
important commit because it "pulled" the change `R` into the main
706+
branch.
707+
576708
The `--simplify-by-decoration` option allows you to view only the
577709
big picture of the topology of the history, by omitting commits
578710
that are not referenced by tags. Commits are marked as !TREESAME

object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct object_array {
5959

6060
/*
6161
* object flag allocation:
62-
* revision.h: 0---------10 25----28
62+
* revision.h: 0---------10 15 25----28
6363
* fetch-pack.c: 01
6464
* negotiator/default.c: 2--5
6565
* walker.c: 0-2

revision.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,19 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
870870
}
871871
parent->next = NULL;
872872
commit->parents = parent;
873-
commit->object.flags |= TREESAME;
873+
874+
/*
875+
* A merge commit is a "diversion" if it is not
876+
* TREESAME to its first parent but is TREESAME
877+
* to a later parent. In the simplified history,
878+
* we "divert" the history walk to the later
879+
* parent. These commits are shown when "show_pulls"
880+
* is enabled, so do not mark the object as
881+
* TREESAME here.
882+
*/
883+
if (!revs->show_pulls || !nth_parent)
884+
commit->object.flags |= TREESAME;
885+
874886
return;
875887

876888
case REV_TREE_NEW:
@@ -897,6 +909,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
897909
relevant_change = 1;
898910
else
899911
irrelevant_change = 1;
912+
913+
if (!nth_parent)
914+
commit->object.flags |= PULL_MERGE;
915+
900916
continue;
901917
}
902918
die("bad tree compare for commit %s", oid_to_hex(&commit->object.oid));
@@ -2269,6 +2285,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
22692285
} else if (!strcmp(arg, "--full-diff")) {
22702286
revs->diff = 1;
22712287
revs->full_diff = 1;
2288+
} else if (!strcmp(arg, "--show-pulls")) {
2289+
revs->show_pulls = 1;
22722290
} else if (!strcmp(arg, "--full-history")) {
22732291
revs->simplify_history = 0;
22742292
} else if (!strcmp(arg, "--relative-date")) {
@@ -3023,7 +3041,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
30233041
if (!cnt ||
30243042
(commit->object.flags & UNINTERESTING) ||
30253043
!(commit->object.flags & TREESAME) ||
3026-
(parent = one_relevant_parent(revs, commit->parents)) == NULL)
3044+
(parent = one_relevant_parent(revs, commit->parents)) == NULL ||
3045+
(revs->show_pulls && (commit->object.flags & PULL_MERGE)))
30273046
st->simplified = commit;
30283047
else {
30293048
pst = locate_simplify_state(revs, parent);
@@ -3606,6 +3625,10 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
36063625
/* drop merges unless we want parenthood */
36073626
if (!want_ancestry(revs))
36083627
return commit_ignore;
3628+
3629+
if (revs->show_pulls && (commit->object.flags & PULL_MERGE))
3630+
return commit_show;
3631+
36093632
/*
36103633
* If we want ancestry, then need to keep any merges
36113634
* between relevant commits to tie together topology.

revision.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
#define SYMMETRIC_LEFT (1u<<8)
3535
#define PATCHSAME (1u<<9)
3636
#define BOTTOM (1u<<10)
37+
38+
/* WARNING: This is also used as REACHABLE in commit-graph.c. */
39+
#define PULL_MERGE (1u<<15)
3740
/*
3841
* Indicates object was reached by traversal. i.e. not given by user on
3942
* command-line or stdin.
@@ -43,7 +46,7 @@
4346
*/
4447
#define NOT_USER_GIVEN (1u<<25)
4548
#define TRACK_LINEAR (1u<<26)
46-
#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR)
49+
#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE)
4750

4851
#define TOPO_WALK_EXPLORED (1u<<27)
4952
#define TOPO_WALK_INDEGREE (1u<<28)
@@ -129,6 +132,7 @@ struct rev_info {
129132
no_walk:2,
130133
remove_empty_trees:1,
131134
simplify_history:1,
135+
show_pulls:1,
132136
topo_order:1,
133137
simplify_merges:1,
134138
simplify_by_decoration:1,

t/t6012-rev-list-simplify.sh

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,124 @@ test_expect_success '--full-diff is not affected by --parents' '
154154
test_cmp expected actual
155155
'
156156

157+
#
158+
# Create a new history to demonstrate the value of --show-pulls
159+
# with respect to the subtleties of simplified history, --full-history,
160+
# and --simplify-merges.
161+
#
162+
# .-A---M-----C--N---O---P
163+
# / / \ \ \/ / /
164+
# I B \ R-'`-Z' /
165+
# \ / \/ /
166+
# \ / /\ /
167+
# `---X--' `---Y--'
168+
#
169+
# This example is explained in Documentation/rev-list-options.txt
170+
171+
test_expect_success 'rebuild repo' '
172+
rm -rf .git * &&
173+
git init &&
174+
git switch -c main &&
175+
176+
echo base >file &&
177+
git add file &&
178+
test_commit I &&
179+
180+
echo A >file &&
181+
git add file &&
182+
test_commit A &&
183+
184+
git switch -c branchB I &&
185+
echo B >file &&
186+
git add file &&
187+
test_commit B &&
188+
189+
git switch main &&
190+
test_must_fail git merge -m "M" B &&
191+
echo A >file &&
192+
echo B >>file &&
193+
git add file &&
194+
git merge --continue &&
195+
note M &&
196+
197+
echo C >other &&
198+
git add other &&
199+
test_commit C &&
200+
201+
git switch -c branchX I &&
202+
echo X >file &&
203+
git add file &&
204+
test_commit X &&
205+
206+
git switch -c branchR M &&
207+
git merge -m R -Xtheirs X &&
208+
note R &&
209+
210+
git switch main &&
211+
git merge -m N R &&
212+
note N &&
213+
214+
git switch -c branchY M &&
215+
echo Y >y &&
216+
git add y &&
217+
test_commit Y &&
218+
219+
git switch -c branchZ C &&
220+
echo Z >z &&
221+
git add z &&
222+
test_commit Z &&
223+
224+
git switch main &&
225+
git merge -m O Z &&
226+
note O &&
227+
228+
git merge -m P Y &&
229+
note P
230+
'
231+
232+
check_result 'X I' -- file
233+
check_result 'N R X I' --show-pulls -- file
234+
235+
check_result 'P O N R X M B A I' --full-history --topo-order -- file
236+
check_result 'N R X M B A I' --simplify-merges --topo-order --show-pulls -- file
237+
check_result 'R X M B A I' --simplify-merges --topo-order -- file
238+
check_result 'N M A I' --first-parent -- file
239+
check_result 'N M A I' --first-parent --show-pulls -- file
240+
241+
# --ancestry-path implies --full-history
242+
check_result 'P O N R M' --topo-order \
243+
--ancestry-path A..HEAD -- file
244+
check_result 'P O N R M' --topo-order \
245+
--show-pulls \
246+
--ancestry-path A..HEAD -- file
247+
check_result 'P O N R M' --topo-order \
248+
--full-history \
249+
--ancestry-path A..HEAD -- file
250+
check_result 'R M' --topo-order \
251+
--simplify-merges \
252+
--ancestry-path A..HEAD -- file
253+
check_result 'N R M' --topo-order \
254+
--simplify-merges --show-pulls \
255+
--ancestry-path A..HEAD -- file
256+
257+
test_expect_success 'log --graph --simplify-merges --show-pulls' '
258+
cat >expect <<-\EOF &&
259+
* N
260+
* R
261+
|\
262+
| * X
263+
* | M
264+
|\ \
265+
| * | B
266+
| |/
267+
* / A
268+
|/
269+
* I
270+
EOF
271+
git log --graph --pretty="%s" \
272+
--simplify-merges --show-pulls \
273+
-- file >actual &&
274+
test_cmp expect actual
275+
'
276+
157277
test_done

0 commit comments

Comments
 (0)