git-hot 0.6__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git_hot-0.6.data/data/share/man/man1/git-hot.1 +333 -0
- git_hot-0.6.data/purelib/git_hot/__init__.py +5 -0
- git_hot-0.6.data/purelib/git_hot/__main__.py +8 -0
- git_hot-0.6.data/purelib/git_hot/daglp.rs +226 -0
- git_hot-0.6.data/purelib/git_hot/lifetime.py +1870 -0
- git_hot-0.6.data/scripts/daglp.exe +0 -0
- git_hot-0.6.data/scripts/daglp.pdb +0 -0
- git_hot-0.6.dist-info/METADATA +361 -0
- git_hot-0.6.dist-info/RECORD +13 -0
- git_hot-0.6.dist-info/WHEEL +5 -0
- git_hot-0.6.dist-info/entry_points.txt +2 -0
- git_hot-0.6.dist-info/licenses/LICENSE +202 -0
- git_hot-0.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
.\" Copyright 1996-2026 Diomidis Spinellis
|
|
2
|
+
.\"
|
|
3
|
+
.\" Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
.\" you may not use this file except in compliance with the License.
|
|
5
|
+
.\" You may obtain a copy of the License at
|
|
6
|
+
.\"
|
|
7
|
+
.\" http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
.\"
|
|
9
|
+
.\" Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
.\" distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
.\" See the License for the specific language governing permissions and
|
|
13
|
+
.\" limitations under the License.
|
|
14
|
+
.\"
|
|
15
|
+
.TH GIT-HOT 1 "May 2026" "git-hot 0.1" "Git Manual"
|
|
16
|
+
.SH NAME
|
|
17
|
+
git-hot \- report Git file and line lifetime churn
|
|
18
|
+
.SH SYNOPSIS
|
|
19
|
+
.SY git
|
|
20
|
+
.B hot
|
|
21
|
+
.RB [ \-h ]
|
|
22
|
+
.RB [ \-d
|
|
23
|
+
.IR dir ]
|
|
24
|
+
.RB [ \-q ]
|
|
25
|
+
.RB [ \-D
|
|
26
|
+
.IR opts ]
|
|
27
|
+
.RB [ \-\-color
|
|
28
|
+
.BR always | never ]
|
|
29
|
+
.RB [ \-\-color-domain
|
|
30
|
+
.BR churn | age | lifetime ]
|
|
31
|
+
.RB [ \-\-format
|
|
32
|
+
.IR format ]
|
|
33
|
+
.RI [ ref ]
|
|
34
|
+
.RI [[ \-\- ]
|
|
35
|
+
.IR path ]
|
|
36
|
+
.YS
|
|
37
|
+
.SY git-hot
|
|
38
|
+
.RB [ \-h ]
|
|
39
|
+
.RB [ \-d
|
|
40
|
+
.IR dir ]
|
|
41
|
+
.RB [ \-q ]
|
|
42
|
+
.RB [ \-D
|
|
43
|
+
.IR opts ]
|
|
44
|
+
.RB [ \-\-color
|
|
45
|
+
.BR always | never ]
|
|
46
|
+
.RB [ \-\-color-domain
|
|
47
|
+
.BR churn | age | lifetime ]
|
|
48
|
+
.RB [ \-\-format
|
|
49
|
+
.IR format ]
|
|
50
|
+
.RI [ ref ]
|
|
51
|
+
.RI [[ \-\- ]
|
|
52
|
+
.IR path ]
|
|
53
|
+
.YS
|
|
54
|
+
.SH DESCRIPTION
|
|
55
|
+
.B git hot
|
|
56
|
+
is a Git extension that reports how "hot" the current files or lines in a
|
|
57
|
+
repository are, using line lifetime and churn information derived from Git
|
|
58
|
+
history.
|
|
59
|
+
It walks the repository's topologically ordered longest commit path, streams
|
|
60
|
+
zero-context diffs through the code lifetime processor, and reports metrics
|
|
61
|
+
for the files or lines that are alive at the selected revision.
|
|
62
|
+
.PP
|
|
63
|
+
When invoked without
|
|
64
|
+
.IR path ,
|
|
65
|
+
.B git hot
|
|
66
|
+
prints one line per current source file, sorted by path.
|
|
67
|
+
The default columns are the maximum line churn in the file, the median changed
|
|
68
|
+
line lifetime in days, the median live line age in days, and the path.
|
|
69
|
+
.PP
|
|
70
|
+
When
|
|
71
|
+
.I path
|
|
72
|
+
is specified,
|
|
73
|
+
.B git hot
|
|
74
|
+
prints the reconstructed contents of that file, with each line preceded by its
|
|
75
|
+
churn count by default.
|
|
76
|
+
The path form follows Git's usual revision/path convention; use
|
|
77
|
+
.B \-\-
|
|
78
|
+
to disambiguate a path from a revision.
|
|
79
|
+
.PP
|
|
80
|
+
Unless quiet mode is selected,
|
|
81
|
+
.B git hot
|
|
82
|
+
reports progress on standard error.
|
|
83
|
+
When standard output is a terminal, output is sent through the configured Git
|
|
84
|
+
pager.
|
|
85
|
+
.SH OPTIONS
|
|
86
|
+
.TP
|
|
87
|
+
.B \-h, \-\-help
|
|
88
|
+
Show usage information and exit.
|
|
89
|
+
.TP
|
|
90
|
+
.BI "\-d " dir ", \-\-dir " dir
|
|
91
|
+
Reconstruct source files below
|
|
92
|
+
.I dir
|
|
93
|
+
with each line preceded by its churn count.
|
|
94
|
+
This option also leaves the normal selected output behavior in effect.
|
|
95
|
+
.TP
|
|
96
|
+
.BI "\-\-format " format
|
|
97
|
+
Format file or line output using a restricted Python f-string expression.
|
|
98
|
+
The available fields depend on whether
|
|
99
|
+
.B git hot
|
|
100
|
+
is producing file metrics or reconstructed line output; see
|
|
101
|
+
.BR "FORMAT STRINGS" .
|
|
102
|
+
.TP
|
|
103
|
+
.B \-q, \-\-quiet
|
|
104
|
+
Suppress progress output.
|
|
105
|
+
.TP
|
|
106
|
+
.BI "\-D " opts ", \-\-debug " opts
|
|
107
|
+
Enable debugging output selected by letters in
|
|
108
|
+
.IR opts .
|
|
109
|
+
Useful flags include
|
|
110
|
+
.B g
|
|
111
|
+
to show Git invocations,
|
|
112
|
+
.B H
|
|
113
|
+
to show commit headers,
|
|
114
|
+
.B D
|
|
115
|
+
to show diff headers,
|
|
116
|
+
.B E
|
|
117
|
+
to show diff extended headers,
|
|
118
|
+
.B L
|
|
119
|
+
to show line-of-code processing,
|
|
120
|
+
.B P
|
|
121
|
+
to show change-set pushes,
|
|
122
|
+
.B C
|
|
123
|
+
to show commit-set changes,
|
|
124
|
+
.B S
|
|
125
|
+
to show splicing operations,
|
|
126
|
+
.B R
|
|
127
|
+
to reconstruct repository contents, and
|
|
128
|
+
.B @
|
|
129
|
+
to show range headers.
|
|
130
|
+
.TP
|
|
131
|
+
.BI "\-\-color " when
|
|
132
|
+
Control ANSI color output.
|
|
133
|
+
.I when
|
|
134
|
+
must be
|
|
135
|
+
.B always
|
|
136
|
+
or
|
|
137
|
+
.BR never .
|
|
138
|
+
When this option is omitted, color is enabled only when standard output is a
|
|
139
|
+
terminal.
|
|
140
|
+
.TP
|
|
141
|
+
.BI "\-\-color-domain " domain
|
|
142
|
+
Choose the metric used for automatic color ranking.
|
|
143
|
+
.I domain
|
|
144
|
+
must be
|
|
145
|
+
.BR churn ,
|
|
146
|
+
.BR age ,
|
|
147
|
+
or
|
|
148
|
+
.BR lifetime .
|
|
149
|
+
The default is
|
|
150
|
+
.BR churn .
|
|
151
|
+
.SH ARGUMENTS
|
|
152
|
+
.TP
|
|
153
|
+
.I ref
|
|
154
|
+
Git revision or reference to analyze.
|
|
155
|
+
If omitted,
|
|
156
|
+
.B git hot
|
|
157
|
+
uses the repository's default revision selection as provided by
|
|
158
|
+
.BR "git log" .
|
|
159
|
+
.TP
|
|
160
|
+
.I path
|
|
161
|
+
Limit analysis to one path and print reconstructed line details for that path.
|
|
162
|
+
At most one path may be specified.
|
|
163
|
+
Use
|
|
164
|
+
.B \-\-
|
|
165
|
+
before the path when the path could be parsed as a revision, or when no
|
|
166
|
+
.I ref
|
|
167
|
+
is supplied.
|
|
168
|
+
.SH OUTPUT
|
|
169
|
+
.SS File Metrics
|
|
170
|
+
The default repository-wide output is equivalent to the format
|
|
171
|
+
.PP
|
|
172
|
+
.EX
|
|
173
|
+
{max(churn):5d} {days(median(changed_lifetime)):5d} {days(median(line_age)):5d} {path}
|
|
174
|
+
.EE
|
|
175
|
+
.PP
|
|
176
|
+
The columns are:
|
|
177
|
+
.TP
|
|
178
|
+
.B max churn
|
|
179
|
+
The maximum number of changes recorded for any live line in the file.
|
|
180
|
+
.TP
|
|
181
|
+
.B median changed lifetime
|
|
182
|
+
The median time, in rounded integer days, between changes of lines in the file.
|
|
183
|
+
.TP
|
|
184
|
+
.B median line age
|
|
185
|
+
The median age, in rounded integer days, of the file's live lines at the
|
|
186
|
+
analyzed revision.
|
|
187
|
+
.TP
|
|
188
|
+
.B path
|
|
189
|
+
The repository path.
|
|
190
|
+
.SS Path Output
|
|
191
|
+
For a selected
|
|
192
|
+
.IR path ,
|
|
193
|
+
the default output is equivalent to:
|
|
194
|
+
.PP
|
|
195
|
+
.EX
|
|
196
|
+
{churn:>{5}d} {line}
|
|
197
|
+
.EE
|
|
198
|
+
.PP
|
|
199
|
+
Each output line represents one live reconstructed line from the selected file.
|
|
200
|
+
.SH FORMAT STRINGS
|
|
201
|
+
.B git hot
|
|
202
|
+
evaluates
|
|
203
|
+
.B \-\-format
|
|
204
|
+
as a Python f-string with a restricted set of names.
|
|
205
|
+
The same option formats file-metric output when no path is selected, and line
|
|
206
|
+
output when a path is selected.
|
|
207
|
+
.PP
|
|
208
|
+
Common helper names are:
|
|
209
|
+
.BR days ,
|
|
210
|
+
.BR max ,
|
|
211
|
+
.BR min ,
|
|
212
|
+
.BR median ,
|
|
213
|
+
.BR mean ,
|
|
214
|
+
.BR quartile_rank ,
|
|
215
|
+
.BR color ,
|
|
216
|
+
.BR color_reset ,
|
|
217
|
+
.BR list ,
|
|
218
|
+
and
|
|
219
|
+
.BR map .
|
|
220
|
+
.PP
|
|
221
|
+
File-metric format strings may use:
|
|
222
|
+
.BR path ,
|
|
223
|
+
.BR churn ,
|
|
224
|
+
.BR changed_lifetime ,
|
|
225
|
+
.BR change_lifetime ,
|
|
226
|
+
.BR line_age ,
|
|
227
|
+
.BR line_churns ,
|
|
228
|
+
.BR line_change_lifetimes ,
|
|
229
|
+
.BR line_ages ,
|
|
230
|
+
.BR file_line_churns ,
|
|
231
|
+
.BR file_line_change_lifetimes ,
|
|
232
|
+
.BR file_line_ages ,
|
|
233
|
+
.BR repo_line_churns ,
|
|
234
|
+
.BR repo_line_change_lifetimes ,
|
|
235
|
+
and
|
|
236
|
+
.BR repo_line_ages .
|
|
237
|
+
.PP
|
|
238
|
+
Line format strings may use:
|
|
239
|
+
.BR churn ,
|
|
240
|
+
.BR age ,
|
|
241
|
+
.BR hash ,
|
|
242
|
+
.BR change_lifetimes ,
|
|
243
|
+
.BR lifetime_median ,
|
|
244
|
+
.BR lifetime_mean ,
|
|
245
|
+
.BR birthtime ,
|
|
246
|
+
.BR line ,
|
|
247
|
+
.BR file_line_churns ,
|
|
248
|
+
.BR file_line_ages ,
|
|
249
|
+
.BR file_line_change_lifetimes ,
|
|
250
|
+
.BR repo_line_churns ,
|
|
251
|
+
.BR repo_line_ages ,
|
|
252
|
+
and
|
|
253
|
+
.BR repo_line_change_lifetimes .
|
|
254
|
+
.PP
|
|
255
|
+
If the format string calls
|
|
256
|
+
.BR color() ,
|
|
257
|
+
.B git hot
|
|
258
|
+
assumes color is handled explicitly and appends a reset sequence when color is
|
|
259
|
+
enabled.
|
|
260
|
+
Otherwise, enabled color is applied automatically using the selected
|
|
261
|
+
.BR \-\-color-domain .
|
|
262
|
+
.SH EXAMPLES
|
|
263
|
+
.PP
|
|
264
|
+
Show hot files in the current repository:
|
|
265
|
+
.EX
|
|
266
|
+
$ git hot
|
|
267
|
+
.EE
|
|
268
|
+
.PP
|
|
269
|
+
Show hot files at
|
|
270
|
+
.BR HEAD :
|
|
271
|
+
.EX
|
|
272
|
+
$ git hot HEAD
|
|
273
|
+
.EE
|
|
274
|
+
.PP
|
|
275
|
+
Show line churn for one file:
|
|
276
|
+
.EX
|
|
277
|
+
$ git hot -- src/main.py
|
|
278
|
+
.EE
|
|
279
|
+
.PP
|
|
280
|
+
Show line ages and birth commits for one file:
|
|
281
|
+
.EX
|
|
282
|
+
$ git hot -q --format '{days(age)} {hash[:7]} {line}' -- src/main.py
|
|
283
|
+
.EE
|
|
284
|
+
.PP
|
|
285
|
+
Reconstruct all source files with churn prefixes below
|
|
286
|
+
.BR hot-tree :
|
|
287
|
+
.EX
|
|
288
|
+
$ git hot --dir hot-tree HEAD
|
|
289
|
+
.EE
|
|
290
|
+
.SH FILES
|
|
291
|
+
.TP
|
|
292
|
+
.I git-hot
|
|
293
|
+
The executable Git extension.
|
|
294
|
+
When installed on
|
|
295
|
+
.BR PATH ,
|
|
296
|
+
Git can invoke it as
|
|
297
|
+
.BR "git hot" .
|
|
298
|
+
.TP
|
|
299
|
+
.I daglp
|
|
300
|
+
Helper program used to compute the longest path through the repository commit
|
|
301
|
+
DAG.
|
|
302
|
+
.SH NOTES
|
|
303
|
+
.B git hot
|
|
304
|
+
expects to run inside a Git repository and requires
|
|
305
|
+
.B daglp
|
|
306
|
+
to be available on
|
|
307
|
+
.BR PATH .
|
|
308
|
+
It uses Git commands including
|
|
309
|
+
.BR "git log" ,
|
|
310
|
+
.BR "git show" ,
|
|
311
|
+
and
|
|
312
|
+
.BR "git diff" ,
|
|
313
|
+
with rename and copy detection enabled.
|
|
314
|
+
.PP
|
|
315
|
+
Only source-code files are reported in file-metric output.
|
|
316
|
+
Binary files and deleted files are skipped.
|
|
317
|
+
.SH EXIT STATUS
|
|
318
|
+
.TP
|
|
319
|
+
.B 0
|
|
320
|
+
Successful completion.
|
|
321
|
+
.TP
|
|
322
|
+
.B 1
|
|
323
|
+
A processing error occurred, such as an invalid output format or a failed
|
|
324
|
+
helper command.
|
|
325
|
+
.TP
|
|
326
|
+
.B 2
|
|
327
|
+
Command-line usage error reported by the argument parser.
|
|
328
|
+
.SH SEE ALSO
|
|
329
|
+
.BR git (1),
|
|
330
|
+
.BR git-log (1),
|
|
331
|
+
.BR git-diff (1),
|
|
332
|
+
.BR git-show (1),
|
|
333
|
+
.BR less (1)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 1996-2026 Diomidis Spinellis
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* Given as input a topologically sorted list of each commit's parents,
|
|
18
|
+
* output the longest path of the DAG from the beginning (the oldest commit)
|
|
19
|
+
* to the end (the newest one).
|
|
20
|
+
* See https://en.wikipedia.org/wiki/Longest_path_problem#Acyclic_graphs_and_critical_paths
|
|
21
|
+
* The input should come from
|
|
22
|
+
* git log --topo-order --pretty=format:'%H %at %P'
|
|
23
|
+
* The output is "SHA identifier" lines.
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
use std::collections::HashMap;
|
|
28
|
+
use std::env;
|
|
29
|
+
use std::fs::File;
|
|
30
|
+
use std::io::{self, BufRead, BufReader};
|
|
31
|
+
use std::process;
|
|
32
|
+
|
|
33
|
+
const DEBUG: bool = false;
|
|
34
|
+
|
|
35
|
+
#[derive(Default)]
|
|
36
|
+
struct Vertex {
|
|
37
|
+
name: String,
|
|
38
|
+
// Author/commit time, SHA, bug ids, or any other tracked commit element.
|
|
39
|
+
identifier: String,
|
|
40
|
+
// Longest path ending here; None means it has not yet been calculated.
|
|
41
|
+
max_length: Option<i32>,
|
|
42
|
+
// Parent commits of this commit.
|
|
43
|
+
edges: Vec<String>,
|
|
44
|
+
// The next vertex in the recorded longest path.
|
|
45
|
+
lp_from: Option<String>,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl Vertex {
|
|
49
|
+
fn new(name: &str, identifier: &str) -> Self {
|
|
50
|
+
Self {
|
|
51
|
+
name: name.to_string(),
|
|
52
|
+
identifier: identifier.to_string(),
|
|
53
|
+
max_length: None,
|
|
54
|
+
edges: Vec::new(),
|
|
55
|
+
lp_from: None,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn get_vertex<'a>(
|
|
61
|
+
vertices: &'a mut HashMap<String, Vertex>,
|
|
62
|
+
name: &str,
|
|
63
|
+
identifier: &str,
|
|
64
|
+
) -> &'a mut Vertex {
|
|
65
|
+
// Return a node by name, adding it to the map if needed.
|
|
66
|
+
let vertex = vertices
|
|
67
|
+
.entry(name.to_string())
|
|
68
|
+
.or_insert_with(|| Vertex::new(name, identifier));
|
|
69
|
+
if !identifier.is_empty() {
|
|
70
|
+
vertex.identifier = identifier.to_string();
|
|
71
|
+
}
|
|
72
|
+
vertex
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn reader_from_args() -> Box<dyn BufRead> {
|
|
76
|
+
let args: Vec<String> = env::args().collect();
|
|
77
|
+
if args.len() == 2 {
|
|
78
|
+
match File::open(&args[1]) {
|
|
79
|
+
Ok(file) => Box::new(BufReader::new(file)),
|
|
80
|
+
Err(error) => {
|
|
81
|
+
eprintln!("{}: {error}", args[1]);
|
|
82
|
+
process::exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
Box::new(BufReader::new(io::stdin()))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn read_graph(
|
|
91
|
+
reader: Box<dyn BufRead>,
|
|
92
|
+
) -> io::Result<(HashMap<String, Vertex>, Vec<String>, Option<String>)> {
|
|
93
|
+
let mut vertices = HashMap::new();
|
|
94
|
+
let mut order = Vec::new();
|
|
95
|
+
let mut end = None;
|
|
96
|
+
|
|
97
|
+
for line in reader.lines() {
|
|
98
|
+
let line = line?;
|
|
99
|
+
let mut parts = line.split_whitespace();
|
|
100
|
+
let Some(node_name) = parts.next() else {
|
|
101
|
+
continue;
|
|
102
|
+
};
|
|
103
|
+
let identifier = parts.next().unwrap_or("");
|
|
104
|
+
|
|
105
|
+
// Read node, adding it to the map if needed.
|
|
106
|
+
get_vertex(&mut vertices, node_name, identifier);
|
|
107
|
+
order.push(node_name.to_string());
|
|
108
|
+
if end.is_none() {
|
|
109
|
+
end = Some(node_name.to_string());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create edges from this commit to its parents.
|
|
113
|
+
for parent_name in parts {
|
|
114
|
+
if DEBUG {
|
|
115
|
+
eprintln!("{parent_name} parent of {node_name}");
|
|
116
|
+
}
|
|
117
|
+
get_vertex(&mut vertices, parent_name, "");
|
|
118
|
+
vertices
|
|
119
|
+
.get_mut(node_name)
|
|
120
|
+
.expect("current vertex exists")
|
|
121
|
+
.edges
|
|
122
|
+
.push(parent_name.to_string());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Ok((vertices, order, end))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn calculate_max_lengths(vertices: &mut HashMap<String, Vertex>, order: &[String]) {
|
|
130
|
+
// Parent vertices that do not appear as full input lines are roots.
|
|
131
|
+
for vertex in vertices.values_mut() {
|
|
132
|
+
vertex.max_length = Some(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Record the maximum path associated with each vertex. The input is
|
|
136
|
+
// topologically sorted newest to oldest, so reverse order gives parents
|
|
137
|
+
// before children and avoids recursion over long commit histories.
|
|
138
|
+
for name in order.iter().rev() {
|
|
139
|
+
let edges = vertices
|
|
140
|
+
.get(name)
|
|
141
|
+
.map(|vertex| vertex.edges.clone())
|
|
142
|
+
.unwrap_or_default();
|
|
143
|
+
let max_path = edges
|
|
144
|
+
.iter()
|
|
145
|
+
.filter_map(|edge| vertices.get(edge).and_then(|vertex| vertex.max_length))
|
|
146
|
+
.max()
|
|
147
|
+
.unwrap_or(-1);
|
|
148
|
+
let length = max_path + 1;
|
|
149
|
+
if DEBUG {
|
|
150
|
+
eprintln!("max_length({name}) = {length}");
|
|
151
|
+
}
|
|
152
|
+
if let Some(vertex) = vertices.get_mut(name) {
|
|
153
|
+
vertex.max_length = Some(length);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn mark_longest_path(
|
|
159
|
+
vertices: &mut HashMap<String, Vertex>,
|
|
160
|
+
order: &[String],
|
|
161
|
+
end: &str,
|
|
162
|
+
) -> Option<String> {
|
|
163
|
+
// Calculate the maximum paths of all vertices.
|
|
164
|
+
calculate_max_lengths(vertices, order);
|
|
165
|
+
|
|
166
|
+
// Obtain and record the longest path. The strict comparison preserves
|
|
167
|
+
// the original first-parent tie behavior.
|
|
168
|
+
let mut current = Some(end.to_string());
|
|
169
|
+
let mut start = Some(end.to_string());
|
|
170
|
+
while let Some(name) = current {
|
|
171
|
+
let edges = vertices
|
|
172
|
+
.get(&name)
|
|
173
|
+
.map(|vertex| vertex.edges.clone())
|
|
174
|
+
.unwrap_or_default();
|
|
175
|
+
let mut longest = None;
|
|
176
|
+
let mut longest_length = None;
|
|
177
|
+
for edge in edges {
|
|
178
|
+
let length = vertices.get(&edge).and_then(|vertex| vertex.max_length);
|
|
179
|
+
if longest.is_none() || length > longest_length {
|
|
180
|
+
longest = Some(edge);
|
|
181
|
+
longest_length = length;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if let Some(parent) = longest {
|
|
186
|
+
if let Some(vertex) = vertices.get_mut(&parent) {
|
|
187
|
+
vertex.lp_from = Some(name);
|
|
188
|
+
}
|
|
189
|
+
start = Some(parent.clone());
|
|
190
|
+
current = Some(parent);
|
|
191
|
+
} else {
|
|
192
|
+
current = None;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
start
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fn print_longest_path(vertices: &HashMap<String, Vertex>, start: Option<String>) {
|
|
200
|
+
// Display the longest path.
|
|
201
|
+
let mut current = start;
|
|
202
|
+
while let Some(name) = current {
|
|
203
|
+
let vertex = vertices.get(&name).expect("path vertex exists");
|
|
204
|
+
println!("{} {}", vertex.name, vertex.identifier);
|
|
205
|
+
current = vertex.lp_from.clone();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn run() -> io::Result<()> {
|
|
210
|
+
let reader = reader_from_args();
|
|
211
|
+
let (mut vertices, order, end) = read_graph(reader)?;
|
|
212
|
+
let Some(end) = end else {
|
|
213
|
+
return Ok(());
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
let start = mark_longest_path(&mut vertices, &order, &end);
|
|
217
|
+
print_longest_path(&vertices, start);
|
|
218
|
+
Ok(())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn main() {
|
|
222
|
+
if let Err(error) = run() {
|
|
223
|
+
eprintln!("{error}");
|
|
224
|
+
process::exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|