@@ -52,13 +52,20 @@ mod graph {
52
52
53
53
/// The OID of the parent node in the smartlog commit graph.
54
54
///
55
- /// This is different from inspecting `commit.parents()`,& since the smartlog
55
+ /// This is different from inspecting `commit.parents()`, since the smartlog
56
56
/// will hide most nodes from the commit graph, including parent nodes.
57
57
pub parent : Option < NonZeroOid > ,
58
58
59
59
/// The OIDs of the children nodes in the smartlog commit graph.
60
60
pub children : Vec < NonZeroOid > ,
61
61
62
+ /// Does this commit have any non-immediate, non-main branch ancestor
63
+ /// nodes in the smartlog commit graph?
64
+ pub has_ancestors : bool ,
65
+
66
+ /// The OIDs of any non-immediate descendant nodes in the smartlog commit graph.
67
+ pub descendants : Vec < NonZeroOid > ,
68
+
62
69
/// Indicates that this is a commit to the main branch.
63
70
///
64
71
/// These commits are considered to be immutable and should never leave the
@@ -80,6 +87,13 @@ mod graph {
80
87
/// where you commit directly to the main branch and then later rewrite the
81
88
/// commit.
82
89
pub is_obsolete : bool ,
90
+
91
+ /// Indicates that this commit has descendants, but that none of them
92
+ /// are included in the graph.
93
+ ///
94
+ /// This allows us to indicate this "false head" to the user. Otherwise,
95
+ /// this commit would look like a normal, descendant-less head.
96
+ pub is_false_head : bool ,
83
97
}
84
98
85
99
/// Graph of commits that the user is working on.
@@ -111,31 +125,27 @@ mod graph {
111
125
}
112
126
}
113
127
114
- /// Find additional commits that should be displayed.
128
+ /// Build the smartlog graph by finding additional commits that should be displayed.
115
129
///
116
130
/// For example, if you check out a commit that has intermediate parent commits
117
131
/// between it and the main branch, those intermediate commits should be shown
118
132
/// (or else you won't get a good idea of the line of development that happened
119
133
/// for this commit since the main branch).
120
134
#[ instrument]
121
- fn walk_from_commits < ' repo > (
135
+ fn build_graph < ' repo > (
122
136
effects : & Effects ,
123
137
repo : & ' repo Repo ,
124
138
dag : & Dag ,
125
- active_heads : & CommitSet ,
139
+ commits : & CommitSet ,
126
140
) -> eyre:: Result < SmartlogGraph < ' repo > > {
127
141
let mut graph: HashMap < NonZeroOid , Node > = {
128
142
let mut result = HashMap :: new ( ) ;
129
- for vertex in commit_set_to_vec ( active_heads ) ? {
143
+ for vertex in commit_set_to_vec ( commits ) ? {
130
144
let vertex = CommitSet :: from ( vertex) ;
131
145
let merge_bases = dag. query ( ) . gca_all ( dag. main_branch_commit . union ( & vertex) ) ?;
132
- let intermediate_commits = if merge_bases. is_empty ( ) ? {
133
- vertex
134
- } else {
135
- dag. query ( ) . range ( merge_bases, vertex) ?
136
- } ;
146
+ let vertices = vertex. union ( & merge_bases) ;
137
147
138
- for oid in commit_set_to_vec ( & intermediate_commits ) ? {
148
+ for oid in commit_set_to_vec ( & vertices ) ? {
139
149
let object = match repo. find_commit ( oid) ? {
140
150
Some ( commit) => NodeObject :: Commit { commit } ,
141
151
None => {
@@ -150,40 +160,103 @@ mod graph {
150
160
object,
151
161
parent : None , // populated below
152
162
children : Vec :: new ( ) , // populated below
163
+ has_ancestors : false ,
164
+ descendants : Vec :: new ( ) , // populated below
153
165
is_main : dag. is_public_commit ( oid) ?,
154
166
is_obsolete : dag. query_obsolete_commits ( ) . contains ( & oid. into ( ) ) ?,
167
+ is_false_head : false ,
155
168
} ,
156
169
) ;
157
170
}
158
171
}
159
172
result
160
173
} ;
161
174
162
- // Find immediate parent-child links.
163
- let links: Vec < ( NonZeroOid , NonZeroOid ) > = {
164
- let non_main_node_oids =
165
- graph. iter ( ) . filter_map (
166
- |( child_oid, node) | if !node. is_main { Some ( child_oid) } else { None } ,
167
- ) ;
168
-
169
- let mut links = Vec :: new ( ) ;
170
- for child_oid in non_main_node_oids {
171
- let parent_vertexes = dag. query ( ) . parents ( CommitSet :: from ( * child_oid) ) ?;
172
- let parent_oids = commit_set_to_vec ( & parent_vertexes) ?;
173
- for parent_oid in parent_oids {
174
- if graph. contains_key ( & parent_oid) {
175
- links. push ( ( * child_oid, parent_oid) )
175
+ let mut immediate_links: Vec < ( NonZeroOid , NonZeroOid ) > = Vec :: new ( ) ;
176
+ let mut non_immediate_links: Vec < ( NonZeroOid , NonZeroOid ) > = Vec :: new ( ) ;
177
+
178
+ let non_main_node_oids =
179
+ graph
180
+ . iter ( )
181
+ . filter_map ( |( child_oid, node) | if !node. is_main { Some ( child_oid) } else { None } ) ;
182
+
183
+ let graph_vertices: CommitSet = graph. keys ( ) . cloned ( ) . collect ( ) ;
184
+ for child_oid in non_main_node_oids {
185
+ let parent_vertices = dag. query ( ) . parents ( CommitSet :: from ( * child_oid) ) ?;
186
+
187
+ // Find immediate parent-child links.
188
+ let parents_in_graph = parent_vertices. intersection ( & graph_vertices) ;
189
+ let parent_oids = commit_set_to_vec ( & parents_in_graph) ?;
190
+ for parent_oid in parent_oids {
191
+ immediate_links. push ( ( * child_oid, parent_oid) )
192
+ }
193
+
194
+ if parent_vertices. count ( ) ? != parents_in_graph. count ( ) ? {
195
+ // Find non-immediate ancestor links.
196
+ let excluded_parents = parent_vertices. difference ( & graph_vertices) ;
197
+ let excluded_parent_oids = commit_set_to_vec ( & excluded_parents) ?;
198
+ for parent_oid in excluded_parent_oids {
199
+ // Find the nearest ancestor that is included in the graph and
200
+ // also on the same branch.
201
+
202
+ let parent_set = CommitSet :: from ( parent_oid) ;
203
+ let merge_base = dag
204
+ . query ( )
205
+ . gca_one ( dag. main_branch_commit . union ( & parent_set) ) ?;
206
+
207
+ let path_to_main_branch = match merge_base {
208
+ Some ( merge_base) => {
209
+ dag. query ( ) . range ( CommitSet :: from ( merge_base) , parent_set) ?
210
+ }
211
+ None => CommitSet :: empty ( ) ,
212
+ } ;
213
+ let nearest_branch_ancestor = dag
214
+ . query ( )
215
+ . heads_ancestors ( path_to_main_branch. intersection ( & graph_vertices) ) ?;
216
+
217
+ let ancestor_oids = commit_set_to_vec ( & nearest_branch_ancestor) ?;
218
+ for ancestor_oid in ancestor_oids. iter ( ) {
219
+ non_immediate_links. push ( ( * ancestor_oid, * child_oid) ) ;
176
220
}
177
221
}
178
222
}
179
- links
180
- } ;
223
+ }
181
224
182
- for ( child_oid, parent_oid) in links . iter ( ) {
225
+ for ( child_oid, parent_oid) in immediate_links . iter ( ) {
183
226
graph. get_mut ( child_oid) . unwrap ( ) . parent = Some ( * parent_oid) ;
184
227
graph. get_mut ( parent_oid) . unwrap ( ) . children . push ( * child_oid) ;
185
228
}
186
229
230
+ for ( ancestor_oid, descendent_oid) in non_immediate_links. iter ( ) {
231
+ graph. get_mut ( descendent_oid) . unwrap ( ) . has_ancestors = true ;
232
+ graph
233
+ . get_mut ( ancestor_oid)
234
+ . unwrap ( )
235
+ . descendants
236
+ . push ( * descendent_oid) ;
237
+ }
238
+
239
+ for ( oid, node) in graph. iter_mut ( ) {
240
+ let oid_set = CommitSet :: from ( * oid) ;
241
+ let is_main_head = !dag. main_branch_commit . intersection ( & oid_set) . is_empty ( ) ?;
242
+ let ancestor_of_main = node. is_main && !is_main_head;
243
+ let has_descendants_in_graph =
244
+ !node. children . is_empty ( ) || !node. descendants . is_empty ( ) ;
245
+
246
+ if ancestor_of_main || has_descendants_in_graph {
247
+ continue ;
248
+ }
249
+
250
+ // This node has no descendants in the graph, so it's a
251
+ // false head if it has *any* (non-obsolete) children.
252
+ let children_not_in_graph = dag
253
+ . query ( )
254
+ . children ( oid_set) ?
255
+ . difference ( & dag. query_obsolete_commits ( ) ) ;
256
+
257
+ node. is_false_head = !children_not_in_graph. is_empty ( ) ?;
258
+ }
259
+
187
260
Ok ( SmartlogGraph { nodes : graph } )
188
261
}
189
262
@@ -224,11 +297,16 @@ mod graph {
224
297
let mut graph = {
225
298
let ( effects, _progress) = effects. start_operation ( OperationType :: WalkCommits ) ;
226
299
227
- for oid in commit_set_to_vec ( commits) ? {
300
+ // HEAD and main head must be included
301
+ let commits = commits
302
+ . union ( & dag. head_commit )
303
+ . union ( & dag. main_branch_commit ) ;
304
+
305
+ for oid in commit_set_to_vec ( & commits) ? {
228
306
mark_commit_reachable ( repo, oid) ?;
229
307
}
230
308
231
- walk_from_commits ( & effects, repo, dag, commits) ?
309
+ build_graph ( & effects, repo, dag, & commits) ?
232
310
} ;
233
311
sort_children ( & mut graph) ;
234
312
Ok ( graph)
@@ -237,6 +315,7 @@ mod graph {
237
315
238
316
mod render {
239
317
use std:: cmp:: Ordering ;
318
+ use std:: collections:: HashSet ;
240
319
use std:: convert:: TryFrom ;
241
320
242
321
use cursive:: theme:: Effect ;
@@ -270,7 +349,16 @@ mod render {
270
349
let mut root_commit_oids: Vec < NonZeroOid > = graph
271
350
. nodes
272
351
. iter ( )
273
- . filter ( |( _oid, node) | node. parent . is_none ( ) )
352
+ . filter ( |( _oid, node) | {
353
+ // Common case: on main w/ no parents in graph, eg a merge base
354
+ node. parent . is_none ( ) && node. is_main ||
355
+ // Pathological cases: orphaned, garbage collected, etc
356
+ node. parent . is_none ( )
357
+ && !node. is_main
358
+ && node. children . is_empty ( )
359
+ && node. descendants . is_empty ( )
360
+ && !node. has_ancestors
361
+ } )
274
362
. map ( |( oid, _node) | oid)
275
363
. copied ( )
276
364
. collect ( ) ;
@@ -337,7 +425,13 @@ mod render {
337
425
( true , true , true ) => glyphs. commit_main_obsolete_head ,
338
426
} ;
339
427
340
- let first_line = {
428
+ let mut lines = vec ! [ ] ;
429
+
430
+ if current_node. has_ancestors {
431
+ lines. push ( StyledString :: plain ( glyphs. vertical_ellipsis . to_string ( ) ) ) ;
432
+ } ;
433
+
434
+ lines. push ( {
341
435
let mut first_line = StyledString :: new ( ) ;
342
436
first_line. append_plain ( cursor) ;
343
437
first_line. append_plain ( " " ) ;
@@ -347,36 +441,50 @@ mod render {
347
441
} else {
348
442
first_line
349
443
}
444
+ } ) ;
445
+
446
+ if current_node. is_false_head {
447
+ lines. push ( StyledString :: plain ( glyphs. vertical_ellipsis . to_string ( ) ) ) ;
350
448
} ;
351
449
352
- let mut lines = vec ! [ first_line] ;
353
450
let children: Vec < _ > = current_node
354
451
. children
355
452
. iter ( )
356
453
. filter ( |child_oid| graph. nodes . contains_key ( child_oid) )
357
454
. copied ( )
358
455
. collect ( ) ;
359
- for ( child_idx, child_oid) in children. iter ( ) . enumerate ( ) {
456
+ let descendants: HashSet < _ > = current_node
457
+ . descendants
458
+ . iter ( )
459
+ . filter ( |descendent_oid| graph. nodes . contains_key ( descendent_oid) )
460
+ . copied ( )
461
+ . collect ( ) ;
462
+ for ( child_idx, child_oid) in children. iter ( ) . chain ( descendants. iter ( ) ) . enumerate ( ) {
360
463
if root_oids. contains ( child_oid) {
361
464
// Will be rendered by the parent.
362
465
continue ;
363
466
}
364
467
365
- if child_idx == children. len ( ) - 1 {
468
+ let is_last_child = child_idx == ( children. len ( ) + descendants. len ( ) ) - 1 ;
469
+ if is_last_child {
366
470
let line = match last_child_line_char {
367
- Some ( _) => StyledString :: plain ( format ! (
471
+ Some ( _) => Some ( StyledString :: plain ( format ! (
368
472
"{}{}" ,
369
473
glyphs. line_with_offshoot, glyphs. slash
370
- ) ) ,
371
-
372
- None => StyledString :: plain ( glyphs. line . to_string ( ) ) ,
474
+ ) ) ) ,
475
+ None if current_node. descendants . is_empty ( ) => {
476
+ Some ( StyledString :: plain ( glyphs. line . to_string ( ) ) )
477
+ }
478
+ None => None ,
373
479
} ;
374
- lines. push ( line)
480
+ if let Some ( line) = line {
481
+ lines. push ( line) ;
482
+ }
375
483
} else {
376
484
lines. push ( StyledString :: plain ( format ! (
377
485
"{}{}" ,
378
486
glyphs. line_with_offshoot, glyphs. slash
379
- ) ) )
487
+ ) ) ) ;
380
488
}
381
489
382
490
let child_output = get_child_output (
@@ -389,7 +497,7 @@ mod render {
389
497
None ,
390
498
) ?;
391
499
for child_line in child_output {
392
- let line = if child_idx == children . len ( ) - 1 {
500
+ let line = if is_last_child {
393
501
match last_child_line_char {
394
502
Some ( last_child_line_char) => StyledStringBuilder :: new ( )
395
503
. append_plain ( format ! ( "{} " , last_child_line_char) )
@@ -399,7 +507,14 @@ mod render {
399
507
}
400
508
} else {
401
509
StyledStringBuilder :: new ( )
402
- . append_plain ( format ! ( "{} " , glyphs. line) )
510
+ . append_plain ( format ! (
511
+ "{} " ,
512
+ if !current_node. descendants. is_empty( ) {
513
+ glyphs. vertical_ellipsis
514
+ } else {
515
+ glyphs. line
516
+ }
517
+ ) )
403
518
. append ( child_line)
404
519
. build ( )
405
520
} ;
@@ -453,13 +568,10 @@ mod render {
453
568
let last_child_line_char = {
454
569
if root_idx == root_oids. len ( ) - 1 {
455
570
None
571
+ } else if has_real_parent ( root_oids[ root_idx + 1 ] , * root_oid) ? {
572
+ Some ( glyphs. line )
456
573
} else {
457
- let next_root_oid = root_oids[ root_idx + 1 ] ;
458
- if has_real_parent ( next_root_oid, * root_oid) ? {
459
- Some ( glyphs. line )
460
- } else {
461
- Some ( glyphs. vertical_ellipsis )
462
- }
574
+ Some ( glyphs. vertical_ellipsis )
463
575
}
464
576
} ;
465
577
@@ -508,8 +620,8 @@ mod render {
508
620
/// as an offset from the current event.
509
621
pub event_id : Option < isize > ,
510
622
511
- /// The commits to render. These commits and their ancestors up to the
512
- /// main branch will be rendered.
623
+ /// The commits to render. These commits, plus any related commits, will
624
+ /// be rendered.
513
625
pub revset : Revset ,
514
626
515
627
pub resolve_revset_options : ResolveRevsetOptions ,
0 commit comments