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
395 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971//! A repository's About, as its Files page shows it beside the files: what
2//! its files say about it (its license, its security policy and the
3//! languages it is written in), who made it (its contributors), who starred
4//! it, and its releases. Methods of the repos service.
5//!
6//! What is read from history and files is worked out in the background for
7//! the default branch's head and kept by commit (services/repos/src/stats.rs),
8//! never on the way to a page: an answer can be for an older commit
9//! (`commit` is not `head`) while the newer one is worked out, or `pending`
10//! when nothing has been worked out yet.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::{RepoPath, Repo};
15use crate::{User, Viewer};
16
17/// One language's share of a repository's code, by bytes.
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct LanguageShare {
21 pub name: String,
22 /// `#rrggbb`, the color it is known by; null for one without.
23 pub color: Option<String>,
24 pub bytes: u64,
25 /// Of the bytes counted, to one decimal place.
26 pub percent: f64,
27}
28
29/// The license a repository's LICENSE (or COPYING) file holds.
30#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct License {
33 /// Its SPDX identifier, such as `MIT` or `Apache-2.0`; null when the
34 /// text is not one g1t recognizes.
35 pub spdx_id: Option<String>,
36 /// What people call it: "MIT License", or "Other" when unrecognized.
37 pub name: String,
38 /// The file it was read from, from the root: `LICENSE`.
39 pub path: String,
40}
41
42/// Who a contributor is: a person with an account (their commits' address
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.43/// is one they confirmed), g1t itself, a known coding agent or bot by the
44/// address on its commits (`coding_agents`), or an author g1t cannot match
45/// to anyone.
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9746#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum ContributorKind {
49 User,
50 G1t,
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.51 Agent,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9752 Author,
53}
54
55/// Commits in one week, the week named by its Monday (`YYYY-MM-DD`, UTC).
56#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
57pub struct WeekCommits {
58 pub week: String,
59 pub commits: u32,
60}
61
62/// Someone whose commits are on the default branch.
63#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct Contributor {
66 pub kind: ContributorKind,
67 /// Their username for a `user` (and `g1t` for g1t); the name on their
68 /// commits otherwise.
69 pub name: String,
70 /// The account's username, for a `user`.
71 #[serde(default)]
72 pub username: Option<String>,
73 /// The uploaded avatar's hash, for a `user` who has one.
74 #[serde(default)]
75 pub avatar: Option<String>,
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.76 /// The coding agent's id (`coding_agents`), for an `agent`.
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub agent: Option<String>,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9779 pub commits: u32,
80 /// RFC 3339: their first and latest commit read.
81 pub first_at: String,
82 pub last_at: String,
83 /// Their commits by week, oldest first, the weeks with none left out.
84 /// Only for the most active contributors (`MAX_CONTRIBUTOR_WEEKS` of
85 /// them); empty for the rest and in the About summary.
86 #[serde(default)]
87 pub weeks: Vec<WeekCommits>,
88}
89
90/// The most contributors kept for a repository.
91pub const MAX_CONTRIBUTORS: usize = 500;
92/// The contributors whose commits are kept week by week.
93pub const MAX_CONTRIBUTOR_WEEKS: usize = 100;
94/// The contributors the About summary names.
95pub const ABOUT_CONTRIBUTORS: usize = 14;
96
97/// Where an answer worked out from the default branch stands.
98#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct Freshness {
101 /// The default branch's head now; null for an empty repository.
102 pub head: Option<String>,
103 /// The commit the answer was worked out for; null when none has been.
104 pub commit: Option<String>,
105 /// RFC 3339: when it was.
106 pub computed_at: Option<String>,
107 /// Nothing has been worked out yet; it is under way. Ask again shortly.
108 pub pending: bool,
109 /// The history or the files were too large to read in full, so the
110 /// answer counts what was read.
111 pub partial: bool,
112}
113
114/// `languages`: a repository's languages by bytes, largest first.
115#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct Languages {
118 #[serde(flatten)]
119 pub freshness: Freshness,
120 pub languages: Vec<LanguageShare>,
121}
122
123/// `contributors`: everyone whose commits are on the default branch, most
124/// commits first, and the repository's commits by week.
125#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct Contributors {
128 #[serde(flatten)]
129 pub freshness: Freshness,
130 /// How many there are; `contributors` lists at most `MAX_CONTRIBUTORS`.
131 pub total: u32,
132 /// The commits read.
133 pub commits: u32,
134 pub contributors: Vec<Contributor>,
135 /// Every commit read, by week, oldest first, including empty weeks.
136 pub weeks: Vec<WeekCommits>,
137}
138
139/// A release: a tag, published with a title and notes.
140#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct Release {
143 /// `rel_…`.
144 pub id: String,
145 pub tag_name: String,
146 /// The commit the tag named when the release was made.
147 pub target: String,
148 /// Its title; the tag's name when it has none.
149 pub name: Option<String>,
150 /// Its notes, Markdown.
151 pub body: String,
152 /// Seen only by those who can push until it is published.
153 pub draft: bool,
154 /// Not ready for everyone: never the latest release.
155 pub prerelease: bool,
156 /// The username of who made it.
157 pub author: Option<String>,
158 /// RFC 3339.
159 pub created_at: String,
160 /// RFC 3339; null while it is a draft.
161 pub published_at: Option<String>,
162 /// Whether it is the latest release: the newest published one that is
163 /// neither a draft nor a prerelease.
164 #[serde(default)]
165 pub latest: bool,
166}
167
168/// The longest release title, and the longest notes.
169pub const MAX_RELEASE_NAME_CHARS: usize = 200;
170pub const MAX_RELEASE_BODY_CHARS: usize = 125_000;
171
172/// Someone who starred a repository.
173#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct Stargazer {
176 pub username: String,
177 pub avatar: Option<String>,
178 /// RFC 3339.
179 pub starred_at: String,
180}
181
182/// A repository someone starred.
183#[derive(Clone, Debug, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct StarredRepo {
186 pub repo: Repo,
187 /// RFC 3339.
188 pub starred_at: String,
189 pub stars: u64,
190}
191
192/// Whether the viewer starred a repository, and how many have.
193#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct Stars {
196 pub starred: bool,
197 pub stars: u64,
198}
199
200/// The About of a repository's Files page, in one answer: what is kept for
201/// the default branch's head, the stars and the releases.
202#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct RepoAbout {
205 #[serde(flatten)]
206 pub freshness: Freshness,
207 pub license: Option<License>,
208 /// The path of its security policy (SECURITY.md at the root, or in
209 /// `.g1t`, `.github` or `docs`), when it has one.
210 pub security_policy: Option<String>,
211 pub languages: Vec<LanguageShare>,
212 /// How many contributors there are, and the most active
213 /// (`ABOUT_CONTRIBUTORS`), without their weeks.
214 pub contributors: u32,
215 pub top_contributors: Vec<Contributor>,
216 pub stars: u64,
217 pub starred: bool,
218 /// Published releases the viewer can see, drafts left out unless they
219 /// can push.
220 pub releases: u64,
221 pub latest_release: Option<Release>,
222}
223
224/// `about`, `languages`, `contributors`, `license`, `stars` and
225/// `releases`: one repository, for the viewer. `about` returns
226/// `Outcome<RepoAbout>`, `languages` `Outcome<Languages>`, `contributors`
227/// `Outcome<Contributors>`, `license` `Outcome<Option<License>>`, `stars`
228/// `Outcome<Stars>` and `releases` `Outcome<Vec<Release>>` (newest first,
229/// at most 100).
230#[derive(Debug, Serialize, Deserialize)]
231pub struct RepoViewArgs {
232 pub path: RepoPath,
233 pub viewer: Viewer,
234}
235
236/// `star`: stars the repository for the actor, or takes their star back.
237/// Returns `Outcome<Stars>`.
238#[derive(Debug, Serialize, Deserialize)]
239pub struct StarArgs {
240 pub path: RepoPath,
241 pub actor: User,
242 pub starred: bool,
243}
244
245/// `stargazers`: who starred a repository, newest first, 100 a page;
246/// `page` from 1. Returns `Outcome<Vec<Stargazer>>`.
247#[derive(Debug, Serialize, Deserialize)]
248pub struct StargazersArgs {
249 pub path: RepoPath,
250 pub viewer: Viewer,
251 #[serde(default)]
252 pub page: Option<u32>,
253}
254
255/// `starred`: the repositories a person starred that the viewer can see,
256/// newest first, at most 100. The person by `username`. Returns
257/// `Vec<StarredRepo>`; empty for an unknown person.
258#[derive(Debug, Serialize, Deserialize)]
259pub struct StarredArgs {
260 pub username: String,
261 pub viewer: Viewer,
262}
263
264/// `release`: one release, by `id` or `tag`, or with `latest` the latest
265/// one. Returns `Outcome<Release>`.
266#[derive(Debug, Serialize, Deserialize)]
267pub struct ReleaseArgs {
268 pub path: RepoPath,
269 pub viewer: Viewer,
270 #[serde(default)]
271 pub id: Option<String>,
272 #[serde(default)]
273 pub tag: Option<String>,
274 #[serde(default)]
275 pub latest: bool,
276}
277
278/// `create_release`: a release of `tag_name`. A tag that does not exist
279/// yet is made at `target` (a branch or commit; the default branch when
280/// absent). Needs the Write role. Returns `Outcome<Release>`.
281#[derive(Debug, Serialize, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub struct CreateReleaseArgs {
284 pub path: RepoPath,
285 pub actor: User,
286 pub tag_name: String,
287 #[serde(default)]
288 pub target: Option<String>,
289 #[serde(default)]
290 pub name: Option<String>,
291 #[serde(default)]
292 pub body: Option<String>,
293 #[serde(default)]
294 pub draft: bool,
295 #[serde(default)]
296 pub prerelease: bool,
297}
298
299/// `update_release`: changes whichever of a release's title, notes, draft
300/// and prerelease are given; an empty title clears it. Publishing a draft
301/// sets `published_at`. Needs the Write role. Returns `Outcome<Release>`.
302#[derive(Debug, Serialize, Deserialize)]
303pub struct UpdateReleaseArgs {
304 pub path: RepoPath,
305 pub actor: User,
306 pub id: String,
307 #[serde(default)]
308 pub name: Option<String>,
309 #[serde(default)]
310 pub body: Option<String>,
311 #[serde(default)]
312 pub draft: Option<bool>,
313 #[serde(default)]
314 pub prerelease: Option<bool>,
315}
316
317/// `delete_release`: deletes a release; its tag stays. Needs the Write
318/// role. Returns `Outcome<bool>`.
319#[derive(Debug, Serialize, Deserialize)]
320pub struct DeleteReleaseArgs {
321 pub path: RepoPath,
322 pub actor: User,
323 pub id: String,
324}
325
326/// Monday of the week `rfc3339` falls in (UTC), as `YYYY-MM-DD`; None when
327/// it cannot be read.
328pub fn week_of(rfc3339: &str) -> Option<String> {
329 let days = days_from_civil_str(rfc3339.get(..10)?)?;
330 // 1970-01-01 was a Thursday: day 0 is three days after a Monday.
331 let monday = days - (days + 3).rem_euclid(7);
332 Some(civil_from_days(monday))
333}
334
335/// The Monday after `week` (a Monday, `YYYY-MM-DD`).
336pub fn next_week(week: &str) -> Option<String> {
337 Some(civil_from_days(days_from_civil_str(week)? + 7))
338}
339
340fn days_from_civil_str(date: &str) -> Option<i64> {
341 let mut parts = date.splitn(3, '-');
342 let year: i64 = parts.next()?.parse().ok()?;
343 let month: i64 = parts.next()?.parse().ok()?;
344 let day: i64 = parts.next()?.parse().ok()?;
345 if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
346 return None;
347 }
348 // Howard Hinnant's days_from_civil.
349 let y = if month <= 2 { year - 1 } else { year };
350 let era = y.div_euclid(400);
351 let yoe = y - era * 400;
352 let mp = (month + 9) % 12;
353 let doy = (153 * mp + 2) / 5 + day - 1;
354 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
355 Some(era * 146_097 + doe - 719_468)
356}
357
358fn civil_from_days(days: i64) -> String {
359 let z = days + 719_468;
360 let era = z.div_euclid(146_097);
361 let doe = z - era * 146_097;
362 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
363 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
364 let mp = (5 * doy + 2) / 153;
365 let day = doy - (153 * mp + 2) / 5 + 1;
366 let month = if mp < 10 { mp + 3 } else { mp - 9 };
367 let year = yoe + era * 400 + i64::from(month <= 2);
368 format!("{year:04}-{month:02}-{day:02}")
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn a_week_is_named_by_its_monday() {
377 // 2026-10-07 is a Wednesday.
378 assert_eq!(week_of("2026-10-07T10:00:00Z").as_deref(), Some("2026-10-05"));
379 assert_eq!(week_of("2026-10-05T00:00:00Z").as_deref(), Some("2026-10-05"));
380 assert_eq!(week_of("2026-10-11T23:59:59Z").as_deref(), Some("2026-10-05"));
381 assert_eq!(week_of("2026-01-01T00:00:00Z").as_deref(), Some("2025-12-29"));
382 assert_eq!(week_of("1970-01-01T00:00:00Z").as_deref(), Some("1969-12-29"));
383 assert_eq!(week_of("nonsense"), None);
384 assert_eq!(next_week("2025-12-29").as_deref(), Some("2026-01-05"));
385 }
386
387 #[test]
388 fn the_about_is_camel_case_between_services() {
389 let about = RepoAbout { contributors: 3, ..RepoAbout::default() };
390 let value = serde_json::to_value(&about).unwrap();
391 assert_eq!(value["topContributors"], serde_json::json!([]));
392 assert_eq!(value["pending"], false);
393 assert_eq!(value["securityPolicy"], serde_json::Value::Null);
394 }
395}

This file's history is long; its oldest lines are credited to the oldest commit read.