Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

`why` no longer hands out commit authors' email addresses. Why a line is the way it is (API why, MCP repository why) returned the blamed commit as git stores it, author address included, to anyone who can read the repository. Its commit is now a WhyCommit: the author is named as git wrote them and by `username`, the g1t account their address belongs to (a confirmed or noreply address, or g1t's own), as list_commits already does, and the address is never returned. The guide, the operation's description, its example and the OpenAPI document say so. Checked: cargo test for g1t-contracts (new: an author is named by account, never by address) and g1t-api (204, the OpenAPI document regenerated), the CLI's (149), cargo check --workspace and g1t-api's wasm32 build.

syntaqxcommitted Parentc0fad9eBrowse files
6 files+67−60/6 viewed
+1−1
15291529 "The decision records on an issue (with those on its pull requests) or on a pull request, oldest first: each with its kind (proposal, claim, integration_failure, integration_passed, fix, approval, merge or note), its confidence (claimed: someone said so; tested: a check or a combination of pull requests proved it; approved: a person approved it), who recorded it (absent for g1t), its summary and its evidence. Issues and pull requests share numbers, so number names either."
15301530 }
15311531 Op::Why => {
1532− "Why a line of a file is the way it is: the commit that last changed it (by blame, at ref or the default branch), the merged pull request it arrived in, that pull request's issue, the decision records on the pull request, and the session entries recorded around that commit (what was said and the tool calls that touched the file, at most eight). pull, issue, decisions and sessions are empty when the commit did not arrive through a pull request."
1532+ "Why a line of a file is the way it is: the commit that last changed it (by blame, at ref or the default branch; its author named as git wrote them, with `username`, the g1t account their address belongs to, when one does, and never the address), the merged pull request it arrived in, that pull request's issue, the decision records on the pull request, and the session entries recorded around that commit (what was said and the tool calls that touched the file, at most eight). pull, issue, decisions and sessions are empty when the commit did not arrive through a pull request."
15331533 }
15341534 Op::MarkPullRequestReady => {
15351535 "Mark a draft pull request ready for review. Push your commits first. The summary becomes its description and should say what changed and why."
+1−1
31433143 "message": "Read retry_limit, as #12 names it",
31443144 "author": {
31453145 "name": "Ana Lima",
3146− "email": "ana@example.com"
3146+ "username": "ana"
31473147 },
31483148 "parents": [
31493149 "4be1c0d9a2f3e4b5c6d7e8f90a1b2c3d4e5f6a7b"
+6−2
77 //! request's issue, its decision records, and the session entries recorded
88 //! around the commit.
99
10−use g1t_contracts::decisions::{Decision, ListDecisionsArgs, Why, WhyIssue, WhyPull};
10+use g1t_contracts::decisions::{Decision, ListDecisionsArgs, Why, WhyCommit, WhyIssue, WhyPull};
1111 use g1t_contracts::repos::{Blame, BlameArgs, Commit, LogArgs, RepoPath};
1212 use g1t_contracts::work::{IssueDetail, ListPullsArgs, Pull, PullStatus, SessionEntry, SessionEntryKind, State, ViewArgs};
1313 use g1t_contracts::{FailureCode, Outcome, Viewer};
143143 Outcome::Ok(blame) => blame,
144144 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
145145 };
146− let Some(commit) = commit_of_line(&blame, line).cloned() else {
146+ let Some(found) = commit_of_line(&blame, line).cloned() else {
147147 let lines = blame.ranges.last().map_or(0, |range| range.end);
148148 return Ok(Outcome::fail(
149149 FailureCode::Invalid,
168168 Outcome::Ok(pulls) => pulls,
169169 Outcome::Fail(_) => Vec::new(),
170170 };
171+ // Who wrote it, by account: the address itself is never returned.
172+ let authors = crate::commits::read_authors(services, &[&found]).await;
173+ let username = crate::commits::username(&found, &authors).map(str::to_owned);
174+ let commit = WhyCommit::of(found, username);
171175 let mut pull = pull_for_commit(&merged, &[], &commit.hash).cloned();
172176 if pull.is_none() && !merged.is_empty() {
173177 let log: Outcome<Vec<Commit>> = g1t_kit::call(
+1−1
108108
109109 | Field | |
110110 | --- | --- |
111−| `commit` | The commit that last changed the line: `hash`, `message`, `author`, `parents`, `authored_at`. |
111+| `commit` | The commit that last changed the line: `hash`, `message`, `author` (`name` as git wrote it, and `username`, the g1t account the author's address belongs to, when one does; never the address), `parents`, `authored_at`. |
112112 | `pull` | The merged pull request it arrived in: `number`, `title`, `author`, `agent`, `merged_by`, `merged_at`. Absent when it did not arrive through one. |
113113 | `issue` | That pull request's issue: `number`, `title`, `state` and `body` (cut at 900 characters). |
114114 | `decisions` | The [decision records](#decision-records) on the pull request, oldest first. |
+0−0

Binary or large file; its contents are not shown.

+58−1
231231 pub body: String,
232232 }
233233
234+/// The commit `why` names. Its author is named as git wrote them and by the
235+/// g1t account their address belongs to, when one does; the address itself
236+/// is never shown.
237+#[derive(Clone, Debug, Serialize, Deserialize)]
238+#[serde(rename_all = "camelCase")]
239+pub struct WhyCommit {
240+ pub hash: String,
241+ pub tree_hash: String,
242+ pub message: String,
243+ pub author: WhyAuthor,
244+ pub parents: Vec<String>,
245+ /// RFC 3339.
246+ pub authored_at: String,
247+}
248+
249+/// Who wrote a commit, without their address.
250+#[derive(Clone, Debug, Serialize, Deserialize)]
251+pub struct WhyAuthor {
252+ pub name: String,
253+ /// The g1t account the author's address belongs to: a confirmed or
254+ /// noreply address of one, or g1t's own.
255+ #[serde(default, skip_serializing_if = "Option::is_none")]
256+ pub username: Option<String>,
257+}
258+
259+impl WhyCommit {
260+ /// `commit` as `why` shows it, its author's account `username` when known.
261+ pub fn of(commit: Commit, username: Option<String>) -> WhyCommit {
262+ WhyCommit {
263+ hash: commit.hash,
264+ tree_hash: commit.tree_hash,
265+ message: commit.message,
266+ author: WhyAuthor { name: commit.author.name, username },
267+ parents: commit.parents,
268+ authored_at: commit.authored_at,
269+ }
270+ }
271+}
272+
234273 /// `why`: why a line is the way it is. The commit that last changed it,
235274 /// the merged pull request it arrived in, that pull request's issue, the
236275 /// decisions recorded on them, and the session entries behind the commit.
239278 pub struct Why {
240279 pub path: String,
241280 pub line: u32,
242− pub commit: Commit,
281+ pub commit: WhyCommit,
243282 pub pull: Option<WhyPull>,
244283 pub issue: Option<WhyIssue>,
245284 pub decisions: Vec<Decision>,
251290 use super::*;
252291
253292 #[test]
293+ fn why_names_an_author_by_account_never_by_address() {
294+ let commit = Commit {
295+ hash: "7d3e".into(),
296+ tree_hash: "c1d2".into(),
297+ message: "Read retry_limit".into(),
298+ author: crate::repos::Signature { name: "Chase Pierce".into(), email: "syntaqx@gmail.com".into() },
299+ parents: vec![],
300+ authored_at: "2026-10-14T00:00:00Z".into(),
301+ };
302+ let known = serde_json::to_value(WhyCommit::of(commit.clone(), Some("syntaqx".into()))).unwrap();
303+ assert_eq!(known["author"], serde_json::json!({ "name": "Chase Pierce", "username": "syntaqx" }));
304+ let unknown = serde_json::to_value(WhyCommit::of(commit, None)).unwrap();
305+ assert_eq!(unknown["author"], serde_json::json!({ "name": "Chase Pierce" }));
306+ assert!(!known.to_string().contains("gmail") && !unknown.to_string().contains("gmail"));
307+ }
308+
309+
310+ #[test]
254311 fn kinds_read_and_write_as_their_names() {
255312 for kind in [
256313 DecisionKind::Proposal,

Comments

Sign in to comment.