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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! The §9 comment byline — who wrote which comment, DERIVED from `git blame`
//! and stored nowhere (bl-236c). Sibling of [`super::journal`] and the same
//! kind of thing: a pure read-side projection over store history, folded into
//! human `bl show` alone. Bedrock `--json` carries stored state, so it never
//! carries a byline and never pays the blame.
//!
//! `bl comment` (§9, bl-d136) appends to the body under a rule and stamps
//! NOTHING — the commit already records who and when, and a second copy in the
//! body would drift. That leaves co-location as the only gap: six comments in
//! one body and no way to tell whose is whose without `git log -p`. This closes
//! it by asking git at render time.
//!
//! **The commit boundary IS the comment boundary.** An append is one commit, so
//! its lines are its lines: group the body's lines by their blame commit, keep
//! the groups whose §5 `bl-op` trailer is `comment`, and hang ONE added render
//! line off each. That is why no marker parsing is needed anywhere here.
//!
//! **The `---` rule is never read** (bl-d136 states it; this does not weaken
//! it). Nothing here searches for a rule, counts rules, splits on one, or
//! suppresses one. No body byte is inspected at all: the body's LINE COUNT is
//! the only thing read off it, to align git's per-line answer with the tail of
//! the file, and every byte passes through to the render unaltered.
//!
//! The byline hangs at the END of its comment's lines, not the start. That is
//! forced by never reading the rule: the append is `\n\n---\n\n{text}\n`, so a
//! comment commit's FIRST lines are always the blank/rule/blank decoration —
//! a byline there sits above the rule and directly under the PREVIOUS comment's
//! text, reading as that one's. The commit's LAST line is always the comment's
//! own last line of text, so a byline there is unambiguous without balls ever
//! knowing a rule exists. (The ball specified "head"; implementation found the
//! misattribution — see `docs/design/bl-236c-comment-attribution.md`.)
//!
//! Degradation is honest and never an error: whatever blame says is what
//! renders. An imported ball collapses onto the import commit (op `import`, not
//! `comment`) and renders bare — that IS who wrote that file. A squashed or
//! rewritten store collapses the same way. A ball whose file git cannot blame
//! at all (never committed, or a store with no history) renders bare too:
//! blame is the one input, and nothing said means nothing rendered.
use HashMap;
use io;
use Path;
use Style;
use crategit;
/// The `\x1f` field separator the byline `--format` uses — a control byte no
/// §5 field carries, so the machine fields split unambiguously.
const FIELD: char = '\u{1f}';
/// The `\x1e` record separator opening each commit's record: a `valueonly`
/// trailer expansion ends in git's own newline, so a record spans lines and
/// only a control byte can delimit one.
const RECORD: char = '\u{1e}';
/// `body` with a byline hung under each of its `comment`-op regions — the human
/// `bl show` projection of a ball's markdown (bl-236c). `rev` is the revision
/// the rendered content comes from (`HEAD` for a live ball, the deletion's
/// parent for a dead one, [`super::history::Dead::rev`]).
///
/// An EMPTY body makes no blame call at all — there is nothing to attribute.
/// Otherwise it costs one `git blame` plus one `git log` over the blamed set,
/// the same cost shape as the journal walk and paid only by the human render.
pub
/// One commit sha per line of the file at `rev`, in file order — git's
/// `blame --porcelain` line map, asked for as structured output rather than
/// hand-parsed (the §5 no-hand-rolled-parser discipline on the read side).
/// The sha a `--porcelain` header line opens, or `None` for git's per-commit
/// metadata lines and the file's own content lines. A metadata line's first
/// token is its key (`author`, `summary`, `previous`, …), never a sha; a
/// content line is TAB-prefixed, and a tab is not a hex digit — so the one
/// test "first token is 40 hex bytes" tells all three apart whatever the file
/// holds, without the renderer looking at the content.
/// The rendered byline for each DISTINCT blamed commit whose §5 `bl-op` trailer
/// reads `comment` — ONE `git log --no-walk` over the blamed set, no history
/// walk. A commit under any other op is simply absent from the map, which is
/// how a `create` body, a `--body` rewrite, an `--edit` and an import all render
/// bare with no case of their own.
///
/// The actor is the `bl-actor` trailer `--as` set, with no author fallback:
/// only a balls `comment` commit reaches the map, and one always carries it.
/// `body` with each blamed group's byline emitted after the group's last line.
///
/// The body is the TAIL of `tasks/<id>.md` (frontmatter, fence, then body), so
/// its lines are the last `n` of git's per-line answer — no frontmatter parsing
/// and no offset arithmetic over the fence. Body bytes are copied verbatim,
/// newline-for-newline: the byline is an ADDED line, never a rewrite.