1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! What comes back from `git`: the listing parsed, and the diagnostic made safe.
//!
//! Everything here is a pure function of bytes another program wrote, which is
//! why it sits apart from [`super::git_ls_files`]: the adapter decides what to
//! run and what a failure means, and this module turns what came out of the
//! pipe into types this repository owns. Nothing here spawns anything, and
//! nothing here depends on the ambient filesystem, so both halves are testable
//! without a repository.
use Utf8PathBuf;
/// Candidate paths, with a count of paths that were not valid UTF-8.
/// The longest run of Git's own diagnostic that is relayed before it is cut.
///
/// A cap rather than a promise: a repository name is not this tool's to trust,
/// and a diagnostic that arrives in kilobytes would bury the message it is
/// supposed to support.
const RELAYED_LIMIT: usize = 1024;
/// Renders `bytes` as one line that is safe to write to a terminal.
///
/// Git's text is the one part of this tool's output that this repository does
/// not author, so it is scrubbed on the way through: a newline would forge a
/// second line of stderr, and a control character — an escape, say — would let
/// a path in the repository drive the terminal reading the diagnostic. Each run
/// of them becomes a single space, so a message written as several lines stays
/// readable as one, and a run at either end disappears rather than becoming a
/// gap. Bytes that are not UTF-8 become the replacement character rather than
/// being dropped, because the text is a diagnostic, not a path: nothing acts
/// on it.
///
/// What is scrubbed is [`is_display_control`] rather than
/// [`char::is_control`] alone: the characters that lay a line out are not all
/// in the control category.
pub
/// Whether `character` is one that must not reach a terminal as text.
///
/// [`char::is_control`] covers the Unicode `Cc` category, which is where an
/// escape lives. The line separator, the paragraph separator, and the
/// bidirectional formatting controls are not control characters, and they do
/// the same work to a relayed diagnostic: a path in the repository that carries
/// one can give the reader a line break this tool did not write, or reverse the
/// direction of the text beside it. Scrubbing them is what makes "one line, and
/// safe to show" true of every byte Git can hand back, rather than of the
/// characters that happen to be in `Cc`.
/// Splits a NUL-terminated byte stream, counting entries that are not UTF-8.
///
/// An empty segment is skipped rather than reported as an empty path, so empty
/// input yields an empty listing. Git terminates even the last path, so the
/// only empty segments are the one after the final NUL and those in input this
/// tool was handed rather than input Git wrote; neither names a file.
///
/// A path that is not UTF-8 is counted, not dropped silently: it cannot be
/// reported as a [`Utf8PathBuf`], and a caller that acts on the listing must be
/// able to say how many files it could not consider.
pub