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
//! Publishing a profile to a per-contributor GitHub issue thread (#5465).
//!
//! Why: a profile written to a local directory is read once by whoever ran it.
//! One issue per contributor, appended to on every run, puts the longitudinal
//! record where the team already reads — and keeps each run's report next to the
//! ones before it, which is the whole point of a longitudinal profile.
//!
//! What: [`GithubIssueConfig`] names the target repository and label,
//! [`issue_title`] builds the title that doubles as the thread's identity, and
//! [`upsert_profile_issue`] hands both to
//! [`crate::collect::github::GitHubClient::upsert_issue_thread`].
//!
//! Auth: the client's existing token. `tga profile --github-issue` builds that
//! client from the `github:` config block, so the write path sends the same
//! personal access token the read path already sends. Switching the write path
//! to a GitHub App installation token is a change to how that client is
//! built — nothing in this module or in `issue_writer.rs` inspects the
//! credential.
//!
//! Test: `reporter_github_tests.rs`.
use crate;
use Result;
use ContributorProfile;
/// Label applied to every profile issue, and searched on to find the thread.
///
/// Changing it orphans every thread opened under the old value — the next run
/// finds nothing and opens a second issue per contributor.
pub const PROFILE_ISSUE_LABEL: &str = "dev-profile";
/// Where a profile's issue thread lives.
///
/// Why: the target repository is deliberately NOT the repository the commits
/// came from — a profile spans repositories, and posting one person's review
/// into a product repo would publish it to everyone watching that repo.
/// What: owner, repo, and the label the thread carries.
/// Test: `github_issue_config_parses_a_slug`.
/// The title of a contributor's profile thread.
///
/// Why: the title is both what a reader sees and how the NEXT run finds this
/// thread, so it has to embed something unique to the contributor. The canonical
/// email is that, and [`upsert_profile_issue`] passes it as the search marker.
/// What: `[dev-profile] <name> <<email>>`.
/// Test: `issue_title_embeds_the_canonical_email`.
/// Create or append to this contributor's profile issue.
///
/// Why: one thread per contributor is what makes the history readable; a run
/// that opened a new issue would scatter it.
/// What: upserts on the canonical email as the marker, posting `markdown` — the
/// same text [`super::Reporter::render`] writes to disk — as the issue body or
/// the new comment. Returns what happened.
///
/// # Errors
///
/// [`super::ProfileError::Git`] wrapping the underlying
/// [`crate::collect::errors::CollectError`] — including
/// `CollectError::GithubApi` with GitHub's own message when the token lacks
/// `issues: write` on the target repository.
///
/// Test: `upsert_profile_issue_appends_to_the_contributors_thread` drives the
/// whole path against a local mock.
pub async
// ─── Unit tests ───────────────────────────────────────────────────────────────