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

A pushed annotated tag starts the workflows that filter its tags: pushing cli-v0.3.5 and then cli-v0.3.6 (alone, git push g1t cli-v0.3.6) to flagon-io/g1t started no CLI release run from .g1t/workflows/cli-release.yml (push: tags: ["cli-v*"]), while cli-v0.3.3 and cli-v0.3.4 had started runs #4 and #5. Those two were lightweight tags; 0.3.5 and 0.3.6 are annotated. After a push, repos checks each pushed ref against where the store actually has it (the store can refuse one ref and accept another) and announces only refs that moved; for a tag it compared the commit the tag reads back as with the hash the push named, which for an annotated tag is the tag object (accae3e8), never the commit it leads to (42a29574), so the tag was taken as refused and no git.push event was published: Actions never heard of it, and nothing about the tag's commit already having run on main was involved. That check now lives in pushed.rs (pushed_to): a branch must be at the commit pushed, as before; a tag whose commit differs from what the push named is taken as pushed unless what it named is itself a commit (then the store left a lightweight tag elsewhere), and a tag is announced at the commit it leads to, as git.push says its after is, so the run is at that commit, as GitHub runs a tag push at the tagged commit. A tag the store cannot read back is still taken as pushed. Deploy: repos. Checked: cargo test -p g1t-repos (367, with new tests for an annotated tag, a lightweight one the store left elsewhere and branches); cargo check -p g1t-repos --target wasm32-unknown-unknown.

G1t-Session:
ses_01m4z69n0reje8qhttqy7sxvwv
syntaqxcommitted Parent5925eaaBrowse files
2 files+79−140/2 viewed
+6−14
25782578 }
25792579 for pushed in announced {
25802580 // The store can refuse one ref and accept another, so each
2581− // branch is checked against where it actually is. A tag the
2582− // store cannot read back is taken as pushed.
2583− let moved = match pushed.branch() {
2584− Some(branch) => stored
2585− .log(branch, 1)
2586− .await?
2587− .first()
2588− .is_some_and(|commit| commit.hash == pushed.after),
2589− None => stored.log(&pushed.git_ref, 1).await.map_or(true, |head| {
2590− head.first().is_none_or(|commit| commit.hash == pushed.after)
2591− }),
2592− };
2581+ // branch and tag is checked against where it actually is, and
2582+ // an annotated tag is announced at its commit (pushed.rs).
2583+ let at = pushed::pushed_to(&stored, pushed).await?;
2584+ let moved = at.is_some();
25932585 // How many commits a branch push brought (push_commits.rs), for
25942586 // the event and the person's contribution calendar. A log the
25952587 // store cannot read leaves the event without a count and the
26112603 {
26122604 worker::console_error!("commits pushed to {} not credited: {error}", repo.name);
26132605 }
2614− if moved {
2606+ if let Some(at) = &at {
26152607 self.publish_git_push(
26162608 &repo,
26172609 &pushed.git_ref,
26182610 pushed.before.as_deref(),
2619− &pushed.after,
2611+ at,
26202612 actor.clone(),
26212613 unscanned,
26222614 caused_by_job.clone(),
+73−0
3232 }
3333 }
3434
35+/// The commit a pushed branch or tag is announced at, once the store has
36+/// the push: `None` when the store kept it elsewhere (it can refuse one
37+/// ref and accept another).
38+///
39+/// A branch must be at the commit the push named. A tag reads back as the
40+/// commit it leads to, which for an annotated tag is not the tag object
41+/// the push named (`cli-v0.3.6`: tag accae3e8, commit 42a29574), so a tag
42+/// whose commit differs is taken as pushed unless what the push named is a
43+/// commit itself, and is announced at that commit, as `git.push` says its
44+/// `after` is. A tag the store cannot read back is taken as pushed.
45+pub(crate) async fn pushed_to<R: GitRepo>(git: &R, pushed: &crate::git_http::Pushed) -> Result<Option<String>> {
46+ if let Some(branch) = pushed.branch() {
47+ let head = git.log(branch, 1).await?;
48+ return Ok(head.first().filter(|commit| commit.hash == pushed.after).map(|_| pushed.after.clone()));
49+ }
50+ let Ok(head) = git.log(&pushed.git_ref, 1).await else {
51+ return Ok(Some(pushed.after.clone()));
52+ };
53+ let Some(commit) = head.into_iter().next() else {
54+ return Ok(Some(pushed.after.clone()));
55+ };
56+ if commit.hash == pushed.after {
57+ return Ok(Some(commit.hash));
58+ }
59+ // A lightweight tag the store left at another commit.
60+ if let Ok(Some(_)) = git.parents(&pushed.after).await {
61+ return Ok(None);
62+ }
63+ Ok(Some(commit.hash))
64+}
65+
3566 /// [`Repos::pushed`] on one repository's commits.
3667 pub(crate) async fn brought_in<R: GitRepo>(git: &R, a: PushedArgs) -> Result<Pushed> {
3768 let before = match &a.before {
131162 #[derive(Default)]
132163 struct Fake {
133164 commits: HashMap<String, Commit>,
165+ /// Branches and tags by name, at the commit each leads to, as the
166+ /// store reads them back.
167+ refs: HashMap<String, String>,
134168 logs: RefCell<u32>,
135169 }
136170
150184 async fn log(&self, git_ref: &str, limit: u32) -> Result<Vec<Commit>> {
151185 *self.logs.borrow_mut() += 1;
152186 let mut out = Vec::new();
187+ let git_ref = self.refs.get(git_ref).map_or(git_ref, String::as_str);
153188 let mut at = self.commits.get(git_ref);
154189 while let Some(commit) = at {
155190 if out.len() as u32 >= limit {
277312 assert_eq!(pushed.commits.len(), 21);
278313 assert_eq!(*git.logs.borrow(), 3);
279314 }
315+
316+ fn push(git_ref: &str, before: Option<&str>, after: &str) -> crate::git_http::Pushed {
317+ crate::git_http::Pushed { git_ref: git_ref.into(), before: before.map(str::to_owned), after: after.into() }
318+ }
319+
320+ fn with_refs(refs: &[(&str, &str)]) -> Fake {
321+ Fake { refs: refs.iter().map(|&(name, at)| (name.to_owned(), at.to_owned())).collect(), ..Fake::with(history()) }
322+ }
323+
324+ #[test]
325+ fn an_annotated_tag_is_announced_at_its_commit() {
326+ // `git push g1t cli-v0.3.6`: the push names the tag object, and the
327+ // store reads the tag back as the commit it leads to, main's head.
328+ let git = with_refs(&[("main", "m2"), ("refs/tags/cli-v0.3.6", "m2")]);
329+ let tag = push("refs/tags/cli-v0.3.6", None, "accae3e8");
330+ assert_eq!(run(pushed_to(&git, &tag)).unwrap().as_deref(), Some("m2"));
331+ // Moved by a new tag object.
332+ let moved = push("refs/tags/cli-v0.3.6", Some("accae3e8"), "b0b0b0b0");
333+ assert_eq!(run(pushed_to(&git, &moved)).unwrap().as_deref(), Some("m2"));
334+ }
335+
336+ #[test]
337+ fn a_lightweight_tag_is_announced_where_the_store_has_it() {
338+ let git = with_refs(&[("refs/tags/v1", "f2")]);
339+ assert_eq!(run(pushed_to(&git, &push("refs/tags/v1", None, "f2"))).unwrap().as_deref(), Some("f2"));
340+ // The push named a commit the store did not move the tag to.
341+ assert_eq!(run(pushed_to(&git, &push("refs/tags/v1", Some("f2"), "m1"))).unwrap(), None);
342+ // A tag the store cannot read back is taken as pushed.
343+ assert_eq!(run(pushed_to(&git, &push("refs/tags/v2", None, "m1"))).unwrap().as_deref(), Some("m1"));
344+ }
345+
346+ #[test]
347+ fn a_branch_is_announced_only_at_the_commit_pushed() {
348+ let git = with_refs(&[("main", "m2")]);
349+ assert_eq!(run(pushed_to(&git, &push("refs/heads/main", Some("m1"), "m2"))).unwrap().as_deref(), Some("m2"));
350+ assert_eq!(run(pushed_to(&git, &push("refs/heads/main", Some("m2"), "f2"))).unwrap(), None);
351+ assert_eq!(run(pushed_to(&git, &push("refs/heads/gone", None, "f2"))).unwrap(), None);
352+ }
280353 }

Comments

Sign in to comment.