@@ -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,32 +125,28 @@ 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
139
public_commits : & CommitSet ,
126
- active_heads : & CommitSet ,
140
+ commits : & CommitSet ,
127
141
) -> eyre:: Result < SmartlogGraph < ' repo > > {
128
142
let mut graph: HashMap < NonZeroOid , Node > = {
129
143
let mut result = HashMap :: new ( ) ;
130
- for vertex in commit_set_to_vec ( active_heads ) ? {
144
+ for vertex in commit_set_to_vec ( commits ) ? {
131
145
let vertex = CommitSet :: from ( vertex) ;
132
146
let merge_bases = dag. query ( ) . gca_all ( dag. main_branch_commit . union ( & vertex) ) ?;
133
- let intermediate_commits = if merge_bases. is_empty ( ) ? {
134
- vertex
135
- } else {
136
- dag. query ( ) . range ( merge_bases, vertex) ?
137
- } ;
147
+ let vertices = vertex. union ( & merge_bases) ;
138
148
139
- for oid in commit_set_to_vec ( & intermediate_commits ) ? {
149
+ for oid in commit_set_to_vec ( & vertices ) ? {
140
150
let object = match repo. find_commit ( oid) ? {
141
151
Some ( commit) => NodeObject :: Commit { commit } ,
142
152
None => {
@@ -151,40 +161,103 @@ mod graph {
151
161
object,
152
162
parent : None , // populated below
153
163
children : Vec :: new ( ) , // populated below
164
+ has_ancestors : false ,
165
+ descendants : Vec :: new ( ) , // populated below
154
166
is_main : public_commits. contains ( & oid. into ( ) ) ?,
155
167
is_obsolete : dag. query_obsolete_commits ( ) . contains ( & oid. into ( ) ) ?,
168
+ is_false_head : false ,
156
169
} ,
157
170
) ;
158
171
}
159
172
}
160
173
result
161
174
} ;
162
175
163
- // Find immediate parent-child links.
164
- let links: Vec < ( NonZeroOid , NonZeroOid ) > = {
165
- let non_main_node_oids =
166
- graph. iter ( ) . filter_map (
167
- |( child_oid, node) | if !node. is_main { Some ( child_oid) } else { None } ,
168
- ) ;
169
-
170
- let mut links = Vec :: new ( ) ;
171
- for child_oid in non_main_node_oids {
172
- let parent_vertexes = dag. query ( ) . parents ( CommitSet :: from ( * child_oid) ) ?;
173
- let parent_oids = commit_set_to_vec ( & parent_vertexes) ?;
174
- for parent_oid in parent_oids {
175
- if graph. contains_key ( & parent_oid) {
176
- links. push ( ( * child_oid, parent_oid) )
176
+ let mut immediate_links: Vec < ( NonZeroOid , NonZeroOid ) > = Vec :: new ( ) ;
177
+ let mut non_immediate_links: Vec < ( NonZeroOid , NonZeroOid ) > = Vec :: new ( ) ;
178
+
179
+ let non_main_node_oids =
180
+ graph
181
+ . iter ( )
182
+ . filter_map ( |( child_oid, node) | if !node. is_main { Some ( child_oid) } else { None } ) ;
183
+
184
+ let graph_vertices: CommitSet = graph. keys ( ) . cloned ( ) . collect ( ) ;
185
+ for child_oid in non_main_node_oids {
186
+ let parent_vertices = dag. query ( ) . parents ( CommitSet :: from ( * child_oid) ) ?;
187
+
188
+ // Find immediate parent-child links.
189
+ let parents_in_graph = parent_vertices. intersection ( & graph_vertices) ;
190
+ let parent_oids = commit_set_to_vec ( & parents_in_graph) ?;
191
+ for parent_oid in parent_oids {
192
+ immediate_links. push ( ( * child_oid, parent_oid) )
193
+ }
194
+
195
+ if parent_vertices. count ( ) ? != parents_in_graph. count ( ) ? {
196
+ // Find non-immediate ancestor links.
197
+ let excluded_parents = parent_vertices. difference ( & graph_vertices) ;
198
+ let excluded_parent_oids = commit_set_to_vec ( & excluded_parents) ?;
199
+ for parent_oid in excluded_parent_oids {
200
+ // Find the nearest ancestor that is included in the graph and
201
+ // also on the same branch.
202
+
203
+ let parent_set = CommitSet :: from ( parent_oid) ;
204
+ let merge_base = dag
205
+ . query ( )
206
+ . gca_one ( dag. main_branch_commit . union ( & parent_set) ) ?;
207
+
208
+ let path_to_main_branch = match merge_base {
209
+ Some ( merge_base) => {
210
+ dag. query ( ) . range ( CommitSet :: from ( merge_base) , parent_set) ?
211
+ }
212
+ None => CommitSet :: empty ( ) ,
213
+ } ;
214
+ let nearest_branch_ancestor = dag
215
+ . query ( )
216
+ . heads_ancestors ( path_to_main_branch. intersection ( & graph_vertices) ) ?;
217
+
218
+ let ancestor_oids = commit_set_to_vec ( & nearest_branch_ancestor) ?;
219
+ for ancestor_oid in ancestor_oids. iter ( ) {
220
+ non_immediate_links. push ( ( * ancestor_oid, * child_oid) ) ;
177
221
}
178
222
}
179
223
}
180
- links
181
- } ;
224
+ }
182
225
183
- for ( child_oid, parent_oid) in links . iter ( ) {
226
+ for ( child_oid, parent_oid) in immediate_links . iter ( ) {
184
227
graph. get_mut ( child_oid) . unwrap ( ) . parent = Some ( * parent_oid) ;
185
228
graph. get_mut ( parent_oid) . unwrap ( ) . children . push ( * child_oid) ;
186
229
}
187
230
231
+ for ( ancestor_oid, descendent_oid) in non_immediate_links. iter ( ) {
232
+ graph. get_mut ( descendent_oid) . unwrap ( ) . has_ancestors = true ;
233
+ graph
234
+ . get_mut ( ancestor_oid)
235
+ . unwrap ( )
236
+ . descendants
237
+ . push ( * descendent_oid) ;
238
+ }
239
+
240
+ for ( oid, node) in graph. iter_mut ( ) {
241
+ let oid_set = CommitSet :: from ( * oid) ;
242
+ let is_main_head = !dag. main_branch_commit . intersection ( & oid_set) . is_empty ( ) ?;
243
+ let ancestor_of_main = node. is_main && !is_main_head;
244
+ let has_descendants_in_graph =
245
+ !node. children . is_empty ( ) || !node. descendants . is_empty ( ) ;
246
+
247
+ if ancestor_of_main || has_descendants_in_graph {
248
+ continue ;
249
+ }
250
+
251
+ // This node has no descendants in the graph, so it's a
252
+ // false head if it has *any* (non-obsolete) children.
253
+ let children_not_in_graph = dag
254
+ . query ( )
255
+ . children ( oid_set) ?
256
+ . difference ( & dag. query_obsolete_commits ( ) ) ;
257
+
258
+ node. is_false_head = !children_not_in_graph. is_empty ( ) ?;
259
+ }
260
+
188
261
Ok ( SmartlogGraph { nodes : graph } )
189
262
}
190
263
@@ -225,12 +298,16 @@ mod graph {
225
298
let mut graph = {
226
299
let ( effects, _progress) = effects. start_operation ( OperationType :: WalkCommits ) ;
227
300
228
- let public_commits = dag. query_public_commits ( ) ?;
229
- for oid in commit_set_to_vec ( commits) ? {
301
+ // HEAD and main head must be included
302
+ let commits = commits
303
+ . union ( & dag. head_commit )
304
+ . union ( & dag. main_branch_commit ) ;
305
+
306
+ for oid in commit_set_to_vec ( & commits) ? {
230
307
mark_commit_reachable ( repo, oid) ?;
231
308
}
232
309
233
- walk_from_commits ( & effects, repo, dag, public_commits , commits) ?
310
+ build_graph ( & effects, repo, dag, dag . query_public_commits ( ) ? , & commits) ?
234
311
} ;
235
312
sort_children ( & mut graph) ;
236
313
Ok ( graph)
@@ -239,6 +316,7 @@ mod graph {
239
316
240
317
mod render {
241
318
use std:: cmp:: Ordering ;
319
+ use std:: collections:: HashSet ;
242
320
use std:: convert:: TryFrom ;
243
321
244
322
use cursive:: theme:: Effect ;
@@ -272,7 +350,16 @@ mod render {
272
350
let mut root_commit_oids: Vec < NonZeroOid > = graph
273
351
. nodes
274
352
. iter ( )
275
- . filter ( |( _oid, node) | node. parent . is_none ( ) )
353
+ . filter ( |( _oid, node) | {
354
+ // Common case: on main w/ no parents in graph, eg a merge base
355
+ node. parent . is_none ( ) && node. is_main ||
356
+ // Pathological cases: orphaned, garbage collected, etc
357
+ node. parent . is_none ( )
358
+ && !node. is_main
359
+ && node. children . is_empty ( )
360
+ && node. descendants . is_empty ( )
361
+ && !node. has_ancestors
362
+ } )
276
363
. map ( |( oid, _node) | oid)
277
364
. copied ( )
278
365
. collect ( ) ;
@@ -339,7 +426,13 @@ mod render {
339
426
( true , true , true ) => glyphs. commit_main_obsolete_head ,
340
427
} ;
341
428
342
- let first_line = {
429
+ let mut lines = vec ! [ ] ;
430
+
431
+ if current_node. has_ancestors {
432
+ lines. push ( StyledString :: plain ( glyphs. vertical_ellipsis . to_string ( ) ) ) ;
433
+ } ;
434
+
435
+ lines. push ( {
343
436
let mut first_line = StyledString :: new ( ) ;
344
437
first_line. append_plain ( cursor) ;
345
438
first_line. append_plain ( " " ) ;
@@ -349,36 +442,50 @@ mod render {
349
442
} else {
350
443
first_line
351
444
}
445
+ } ) ;
446
+
447
+ if current_node. is_false_head {
448
+ lines. push ( StyledString :: plain ( glyphs. vertical_ellipsis . to_string ( ) ) ) ;
352
449
} ;
353
450
354
- let mut lines = vec ! [ first_line] ;
355
451
let children: Vec < _ > = current_node
356
452
. children
357
453
. iter ( )
358
454
. filter ( |child_oid| graph. nodes . contains_key ( child_oid) )
359
455
. copied ( )
360
456
. collect ( ) ;
361
- for ( child_idx, child_oid) in children. iter ( ) . enumerate ( ) {
457
+ let descendants: HashSet < _ > = current_node
458
+ . descendants
459
+ . iter ( )
460
+ . filter ( |descendent_oid| graph. nodes . contains_key ( descendent_oid) )
461
+ . copied ( )
462
+ . collect ( ) ;
463
+ for ( child_idx, child_oid) in children. iter ( ) . chain ( descendants. iter ( ) ) . enumerate ( ) {
362
464
if root_oids. contains ( child_oid) {
363
465
// Will be rendered by the parent.
364
466
continue ;
365
467
}
366
468
367
- if child_idx == children. len ( ) - 1 {
469
+ let is_last_child = child_idx == ( children. len ( ) + descendants. len ( ) ) - 1 ;
470
+ if is_last_child {
368
471
let line = match last_child_line_char {
369
- Some ( _) => StyledString :: plain ( format ! (
472
+ Some ( _) => Some ( StyledString :: plain ( format ! (
370
473
"{}{}" ,
371
474
glyphs. line_with_offshoot, glyphs. slash
372
- ) ) ,
373
-
374
- None => StyledString :: plain ( glyphs. line . to_string ( ) ) ,
475
+ ) ) ) ,
476
+ None if current_node. descendants . is_empty ( ) => {
477
+ Some ( StyledString :: plain ( glyphs. line . to_string ( ) ) )
478
+ }
479
+ None => None ,
375
480
} ;
376
- lines. push ( line)
481
+ if let Some ( line) = line {
482
+ lines. push ( line) ;
483
+ }
377
484
} else {
378
485
lines. push ( StyledString :: plain ( format ! (
379
486
"{}{}" ,
380
487
glyphs. line_with_offshoot, glyphs. slash
381
- ) ) )
488
+ ) ) ) ;
382
489
}
383
490
384
491
let child_output = get_child_output (
@@ -391,7 +498,7 @@ mod render {
391
498
None ,
392
499
) ?;
393
500
for child_line in child_output {
394
- let line = if child_idx == children . len ( ) - 1 {
501
+ let line = if is_last_child {
395
502
match last_child_line_char {
396
503
Some ( last_child_line_char) => StyledStringBuilder :: new ( )
397
504
. append_plain ( format ! ( "{} " , last_child_line_char) )
@@ -401,7 +508,14 @@ mod render {
401
508
}
402
509
} else {
403
510
StyledStringBuilder :: new ( )
404
- . append_plain ( format ! ( "{} " , glyphs. line) )
511
+ . append_plain ( format ! (
512
+ "{} " ,
513
+ if !current_node. descendants. is_empty( ) {
514
+ glyphs. vertical_ellipsis
515
+ } else {
516
+ glyphs. line
517
+ }
518
+ ) )
405
519
. append ( child_line)
406
520
. build ( )
407
521
} ;
@@ -455,13 +569,10 @@ mod render {
455
569
let last_child_line_char = {
456
570
if root_idx == root_oids. len ( ) - 1 {
457
571
None
572
+ } else if has_real_parent ( root_oids[ root_idx + 1 ] , * root_oid) ? {
573
+ Some ( glyphs. line )
458
574
} else {
459
- let next_root_oid = root_oids[ root_idx + 1 ] ;
460
- if has_real_parent ( next_root_oid, * root_oid) ? {
461
- Some ( glyphs. line )
462
- } else {
463
- Some ( glyphs. vertical_ellipsis )
464
- }
575
+ Some ( glyphs. vertical_ellipsis )
465
576
}
466
577
} ;
467
578
@@ -510,8 +621,8 @@ mod render {
510
621
/// as an offset from the current event.
511
622
pub event_id : Option < isize > ,
512
623
513
- /// The commits to render. These commits and their ancestors up to the
514
- /// main branch will be rendered.
624
+ /// The commits to render. These commits, plus any related commits, will
625
+ /// be rendered.
515
626
pub revset : Revset ,
516
627
517
628
pub resolve_revset_options : ResolveRevsetOptions ,
0 commit comments