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
This commit is contained in:
commit
9af3a7cb4d
@ -342,6 +342,12 @@ Default mode::
|
||||
branches if the end result is the same (i.e. merging branches
|
||||
with the same content)
|
||||
|
||||
--show-pulls::
|
||||
Include all commits from the default mode, but also any merge
|
||||
commits that are not TREESAME to the first parent but are
|
||||
TREESAME to a later parent. This mode is helpful for showing
|
||||
the merge commits that "first introduced" a change to a branch.
|
||||
|
||||
--full-history::
|
||||
Same as the default mode, but does not prune some history.
|
||||
|
||||
@ -534,7 +540,7 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`:
|
||||
parent and is TREESAME.
|
||||
--
|
||||
|
||||
Finally, there is a fifth simplification mode available:
|
||||
There is another simplification mode available:
|
||||
|
||||
--ancestry-path::
|
||||
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:
|
||||
L--M
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Before discussing another option, `--show-pulls`, we need to
|
||||
create a new example history.
|
||||
+
|
||||
A common problem users face when looking at simplified history is that a
|
||||
commit they know changed a file somehow does not appear in the file's
|
||||
simplified history. Let's demonstrate a new example and show how options
|
||||
such as `--full-history` and `--simplify-merges` works in that case:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
.-A---M-----C--N---O---P
|
||||
/ / \ \ \/ / /
|
||||
I B \ R-'`-Z' /
|
||||
\ / \/ /
|
||||
\ / /\ /
|
||||
`---X--' `---Y--'
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
For this example, suppose `I` created `file.txt` which was modified by
|
||||
`A`, `B`, and `X` in different ways. The single-parent commits `C`, `Z`,
|
||||
and `Y` do not change `file.txt`. The merge commit `M` was created by
|
||||
resolving the merge conflict to include both changes from `A` and `B`
|
||||
and hence is not TREESAME to either. The merge commit `R`, however, was
|
||||
created by ignoring the contents of `file.txt` at `M` and taking only
|
||||
the contents of `file.txt` at `X`. Hence, `R` is TREESAME to `X` but not
|
||||
`M`. Finally, the natural merge resolution to create `N` is to take the
|
||||
contents of `file.txt` at `R`, so `N` is TREESAME to `R` but not `C`.
|
||||
The merge commits `O` and `P` are TREESAME to their first parents, but
|
||||
not to their second parents, `Z` and `Y` respectively.
|
||||
+
|
||||
When using the default mode, `N` and `R` both have a TREESAME parent, so
|
||||
those edges are walked and the others are ignored. The resulting history
|
||||
graph is:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
I---X
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
When using `--full-history`, Git walks every edge. This will discover
|
||||
the commits `A` and `B` and the merge `M`, but also will reveal the
|
||||
merge commits `O` and `P`. With parent rewriting, the resulting graph is:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
.-A---M--------N---O---P
|
||||
/ / \ \ \/ / /
|
||||
I B \ R-'`--' /
|
||||
\ / \/ /
|
||||
\ / /\ /
|
||||
`---X--' `------'
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
Here, the merge commits `O` and `P` contribute extra noise, as they did
|
||||
not actually contribute a change to `file.txt`. They only merged a topic
|
||||
that was based on an older version of `file.txt`. This is a common
|
||||
issue in repositories using a workflow where many contributors work in
|
||||
parallel and merge their topic branches along a single trunk: manu
|
||||
unrelated merges appear in the `--full-history` results.
|
||||
+
|
||||
When using the `--simplify-merges` option, the commits `O` and `P`
|
||||
disappear from the results. This is because the rewritten second parents
|
||||
of `O` and `P` are reachable from their first parents. Those edges are
|
||||
removed and then the commits look like single-parent commits that are
|
||||
TREESAME to their parent. This also happens to the commit `N`, resulting
|
||||
in a history view as follows:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
.-A---M--.
|
||||
/ / \
|
||||
I B R
|
||||
\ / /
|
||||
\ / /
|
||||
`---X--'
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
In this view, we see all of the important single-parent changes from
|
||||
`A`, `B`, and `X`. We also see the carefully-resolved merge `M` and the
|
||||
not-so-carefully-resolved merge `R`. This is usually enough information
|
||||
to determine why the commits `A` and `B` "disappeared" from history in
|
||||
the default view. However, there are a few issues with this approach.
|
||||
+
|
||||
The first issue is performance. Unlike any previous option, the
|
||||
`--simplify-merges` option requires walking the entire commit history
|
||||
before returning a single result. This can make the option difficult to
|
||||
use for very large repositories.
|
||||
+
|
||||
The second issue is one of auditing. When many contributors are working
|
||||
on the same repository, it is important which merge commits introduced
|
||||
a change into an important branch. The problematic merge `R` above is
|
||||
not likely to be the merge commit that was used to merge into an
|
||||
important branch. Instead, the merge `N` was used to merge `R` and `X`
|
||||
into the important branch. This commit may have information about why
|
||||
the change `X` came to override the changes from `A` and `B` in its
|
||||
commit message.
|
||||
+
|
||||
The `--show-pulls` option helps with both of these issues by adding more
|
||||
merge commits to the history results. If a merge is not TREESAME to its
|
||||
first parent but is TREESAME to a later parent, then that merge is
|
||||
treated as if it "pulled" the change from another branch. When using
|
||||
`--show-pulls` on this example (and no other options) the resulting
|
||||
graph is:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
I---X---R---N
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
Here, the merge commits `R` and `N` are included because they pulled
|
||||
the commits `X` and `R` into the base branch, respectively. These
|
||||
merges are the reason the commits `A` and `B` do not appear in the
|
||||
default history.
|
||||
+
|
||||
When `--show-pulls` is paired with `--simplify-merges`, the
|
||||
graph includes all of the necessary information:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
.-A---M--. N
|
||||
/ / \ /
|
||||
I B R
|
||||
\ / /
|
||||
\ / /
|
||||
`---X--'
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
Notice that since `M` is reachable from `R`, the edge from `N` to `M`
|
||||
was simplified away. However, `N` still appears in the history as an
|
||||
important commit because it "pulled" the change `R` into the main
|
||||
branch.
|
||||
|
||||
The `--simplify-by-decoration` option allows you to view only the
|
||||
big picture of the topology of the history, by omitting commits
|
||||
that are not referenced by tags. Commits are marked as !TREESAME
|
||||
|
2
object.h
2
object.h
@ -59,7 +59,7 @@ struct object_array {
|
||||
|
||||
/*
|
||||
* object flag allocation:
|
||||
* revision.h: 0---------10 25----28
|
||||
* revision.h: 0---------10 15 25----28
|
||||
* fetch-pack.c: 01
|
||||
* negotiator/default.c: 2--5
|
||||
* walker.c: 0-2
|
||||
|
27
revision.c
27
revision.c
@ -870,7 +870,19 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
|
||||
}
|
||||
parent->next = NULL;
|
||||
commit->parents = parent;
|
||||
commit->object.flags |= TREESAME;
|
||||
|
||||
/*
|
||||
* A merge commit is a "diversion" if it is not
|
||||
* TREESAME to its first parent but is TREESAME
|
||||
* to a later parent. In the simplified history,
|
||||
* we "divert" the history walk to the later
|
||||
* parent. These commits are shown when "show_pulls"
|
||||
* is enabled, so do not mark the object as
|
||||
* TREESAME here.
|
||||
*/
|
||||
if (!revs->show_pulls || !nth_parent)
|
||||
commit->object.flags |= TREESAME;
|
||||
|
||||
return;
|
||||
|
||||
case REV_TREE_NEW:
|
||||
@ -897,6 +909,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
|
||||
relevant_change = 1;
|
||||
else
|
||||
irrelevant_change = 1;
|
||||
|
||||
if (!nth_parent)
|
||||
commit->object.flags |= PULL_MERGE;
|
||||
|
||||
continue;
|
||||
}
|
||||
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
|
||||
} else if (!strcmp(arg, "--full-diff")) {
|
||||
revs->diff = 1;
|
||||
revs->full_diff = 1;
|
||||
} else if (!strcmp(arg, "--show-pulls")) {
|
||||
revs->show_pulls = 1;
|
||||
} else if (!strcmp(arg, "--full-history")) {
|
||||
revs->simplify_history = 0;
|
||||
} else if (!strcmp(arg, "--relative-date")) {
|
||||
@ -3023,7 +3041,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
|
||||
if (!cnt ||
|
||||
(commit->object.flags & UNINTERESTING) ||
|
||||
!(commit->object.flags & TREESAME) ||
|
||||
(parent = one_relevant_parent(revs, commit->parents)) == NULL)
|
||||
(parent = one_relevant_parent(revs, commit->parents)) == NULL ||
|
||||
(revs->show_pulls && (commit->object.flags & PULL_MERGE)))
|
||||
st->simplified = commit;
|
||||
else {
|
||||
pst = locate_simplify_state(revs, parent);
|
||||
@ -3606,6 +3625,10 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
|
||||
/* drop merges unless we want parenthood */
|
||||
if (!want_ancestry(revs))
|
||||
return commit_ignore;
|
||||
|
||||
if (revs->show_pulls && (commit->object.flags & PULL_MERGE))
|
||||
return commit_show;
|
||||
|
||||
/*
|
||||
* If we want ancestry, then need to keep any merges
|
||||
* between relevant commits to tie together topology.
|
||||
|
@ -34,6 +34,9 @@
|
||||
#define SYMMETRIC_LEFT (1u<<8)
|
||||
#define PATCHSAME (1u<<9)
|
||||
#define BOTTOM (1u<<10)
|
||||
|
||||
/* WARNING: This is also used as REACHABLE in commit-graph.c. */
|
||||
#define PULL_MERGE (1u<<15)
|
||||
/*
|
||||
* Indicates object was reached by traversal. i.e. not given by user on
|
||||
* command-line or stdin.
|
||||
@ -43,7 +46,7 @@
|
||||
*/
|
||||
#define NOT_USER_GIVEN (1u<<25)
|
||||
#define TRACK_LINEAR (1u<<26)
|
||||
#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR)
|
||||
#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE)
|
||||
|
||||
#define TOPO_WALK_EXPLORED (1u<<27)
|
||||
#define TOPO_WALK_INDEGREE (1u<<28)
|
||||
@ -129,6 +132,7 @@ struct rev_info {
|
||||
no_walk:2,
|
||||
remove_empty_trees:1,
|
||||
simplify_history:1,
|
||||
show_pulls:1,
|
||||
topo_order:1,
|
||||
simplify_merges:1,
|
||||
simplify_by_decoration:1,
|
||||
|
@ -154,4 +154,124 @@ test_expect_success '--full-diff is not affected by --parents' '
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
#
|
||||
# Create a new history to demonstrate the value of --show-pulls
|
||||
# with respect to the subtleties of simplified history, --full-history,
|
||||
# and --simplify-merges.
|
||||
#
|
||||
# .-A---M-----C--N---O---P
|
||||
# / / \ \ \/ / /
|
||||
# I B \ R-'`-Z' /
|
||||
# \ / \/ /
|
||||
# \ / /\ /
|
||||
# `---X--' `---Y--'
|
||||
#
|
||||
# This example is explained in Documentation/rev-list-options.txt
|
||||
|
||||
test_expect_success 'rebuild repo' '
|
||||
rm -rf .git * &&
|
||||
git init &&
|
||||
git switch -c main &&
|
||||
|
||||
echo base >file &&
|
||||
git add file &&
|
||||
test_commit I &&
|
||||
|
||||
echo A >file &&
|
||||
git add file &&
|
||||
test_commit A &&
|
||||
|
||||
git switch -c branchB I &&
|
||||
echo B >file &&
|
||||
git add file &&
|
||||
test_commit B &&
|
||||
|
||||
git switch main &&
|
||||
test_must_fail git merge -m "M" B &&
|
||||
echo A >file &&
|
||||
echo B >>file &&
|
||||
git add file &&
|
||||
git merge --continue &&
|
||||
note M &&
|
||||
|
||||
echo C >other &&
|
||||
git add other &&
|
||||
test_commit C &&
|
||||
|
||||
git switch -c branchX I &&
|
||||
echo X >file &&
|
||||
git add file &&
|
||||
test_commit X &&
|
||||
|
||||
git switch -c branchR M &&
|
||||
git merge -m R -Xtheirs X &&
|
||||
note R &&
|
||||
|
||||
git switch main &&
|
||||
git merge -m N R &&
|
||||
note N &&
|
||||
|
||||
git switch -c branchY M &&
|
||||
echo Y >y &&
|
||||
git add y &&
|
||||
test_commit Y &&
|
||||
|
||||
git switch -c branchZ C &&
|
||||
echo Z >z &&
|
||||
git add z &&
|
||||
test_commit Z &&
|
||||
|
||||
git switch main &&
|
||||
git merge -m O Z &&
|
||||
note O &&
|
||||
|
||||
git merge -m P Y &&
|
||||
note P
|
||||
'
|
||||
|
||||
check_result 'X I' -- file
|
||||
check_result 'N R X I' --show-pulls -- file
|
||||
|
||||
check_result 'P O N R X M B A I' --full-history --topo-order -- file
|
||||
check_result 'N R X M B A I' --simplify-merges --topo-order --show-pulls -- file
|
||||
check_result 'R X M B A I' --simplify-merges --topo-order -- file
|
||||
check_result 'N M A I' --first-parent -- file
|
||||
check_result 'N M A I' --first-parent --show-pulls -- file
|
||||
|
||||
# --ancestry-path implies --full-history
|
||||
check_result 'P O N R M' --topo-order \
|
||||
--ancestry-path A..HEAD -- file
|
||||
check_result 'P O N R M' --topo-order \
|
||||
--show-pulls \
|
||||
--ancestry-path A..HEAD -- file
|
||||
check_result 'P O N R M' --topo-order \
|
||||
--full-history \
|
||||
--ancestry-path A..HEAD -- file
|
||||
check_result 'R M' --topo-order \
|
||||
--simplify-merges \
|
||||
--ancestry-path A..HEAD -- file
|
||||
check_result 'N R M' --topo-order \
|
||||
--simplify-merges --show-pulls \
|
||||
--ancestry-path A..HEAD -- file
|
||||
|
||||
test_expect_success 'log --graph --simplify-merges --show-pulls' '
|
||||
cat >expect <<-\EOF &&
|
||||
* N
|
||||
* R
|
||||
|\
|
||||
| * X
|
||||
* | M
|
||||
|\ \
|
||||
| * | B
|
||||
| |/
|
||||
* / A
|
||||
|/
|
||||
* I
|
||||
EOF
|
||||
git log --graph --pretty="%s" \
|
||||
--simplify-merges --show-pulls \
|
||||
-- file >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user