fix(presentation): handle force-push, branch-create, empty pushes
PushEvent payloads carry `created`, `forced`, `distinct_size`, and `ref` flags that I wasn't consulting — the result on the timeline was "pushed 0 commits" for what were actually branch creations (distinct_size 0 because the commits already existed elsewhere) and force-pushes that didn't change the resulting tree. * created=true → "created branch X in repo" + GitBranchCreate icon * forced + size>0 → "force-pushed N commits to repo:branch" * forced + size==0 → "force-pushed repo:branch" * normal + size>0 → "pushed N commits to repo:branch" (unchanged) * normal + size==0 → "pushed to repo:branch" (no awkward "0 commits") Also: drop the instagram, facebook, and steel-horse-adventures links from the UI header — those represent personae the user no longer wants to surface from rob.tn. Tests: +3 in presentation/github.rs covering the new push branches — 21 total green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -91,24 +91,58 @@ type Reshaped = (
|
||||
|
||||
fn push(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
|
||||
let repo = repo.unwrap_or("(unknown repo)");
|
||||
let size = p
|
||||
.and_then(|v| v.get("distinct_size").or_else(|| v.get("size")))
|
||||
let distinct_size = p
|
||||
.and_then(|v| v.get("distinct_size"))
|
||||
.and_then(Value::as_i64)
|
||||
.unwrap_or(0);
|
||||
let forced = p
|
||||
.and_then(|v| v.get("forced"))
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let created = p
|
||||
.and_then(|v| v.get("created"))
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let branch = p
|
||||
.and_then(|v| v.get("ref"))
|
||||
.and_then(Value::as_str)
|
||||
.map(ref_branch)
|
||||
.unwrap_or("");
|
||||
|
||||
let title = vec![
|
||||
TitleSegment::text(format!(
|
||||
"pushed {size} commit{} to ",
|
||||
if size == 1 { "" } else { "s" }
|
||||
)),
|
||||
repo_link(repo),
|
||||
TitleSegment::text(format!(":{branch}")),
|
||||
];
|
||||
// Branch-creation pushes have distinct_size = 0 (the commits already
|
||||
// existed on another branch) and a different intent than a code push.
|
||||
// Force-pushes and ordinary pushes both render with the GitPush icon
|
||||
// but are phrased differently.
|
||||
let (icon, title) = if created {
|
||||
(
|
||||
TimelineIcon::GitBranchCreate,
|
||||
vec![
|
||||
TitleSegment::text(format!("created branch {branch} in ")),
|
||||
repo_link(repo),
|
||||
],
|
||||
)
|
||||
} else if distinct_size == 0 {
|
||||
let verb = if forced { "force-pushed" } else { "pushed to" };
|
||||
(
|
||||
TimelineIcon::GitPush,
|
||||
vec![
|
||||
TitleSegment::text(format!("{verb} ")),
|
||||
repo_link(repo),
|
||||
TitleSegment::text(format!(":{branch}")),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
let verb = if forced { "force-pushed" } else { "pushed" };
|
||||
let plural = if distinct_size == 1 { "" } else { "s" };
|
||||
(
|
||||
TimelineIcon::GitPush,
|
||||
vec![
|
||||
TitleSegment::text(format!("{verb} {distinct_size} commit{plural} to ")),
|
||||
repo_link(repo),
|
||||
TitleSegment::text(format!(":{branch}")),
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
let commits: Vec<CommitSummary> = p
|
||||
.and_then(|v| v.get("commits"))
|
||||
@@ -148,7 +182,7 @@ fn push(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
|
||||
Some(TimelineBody::Commits { commits })
|
||||
};
|
||||
|
||||
(TimelineIcon::GitPush, title, None, body)
|
||||
(icon, title, None, body)
|
||||
}
|
||||
|
||||
fn pull_request(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
|
||||
@@ -555,6 +589,80 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(item: &TimelineItem) -> String {
|
||||
item.title
|
||||
.iter()
|
||||
.map(|s| match s {
|
||||
TitleSegment::Text { text } => text.clone(),
|
||||
TitleSegment::Link { text, .. } => text.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_branch_creation_uses_create_icon() {
|
||||
let raw = json!({
|
||||
"actor": { "login": "grenade" },
|
||||
"repo": { "name": "grenade/vortex" },
|
||||
"payload": {
|
||||
"ref": "refs/heads/fix-double-panic",
|
||||
"size": 0,
|
||||
"distinct_size": 0,
|
||||
"created": true,
|
||||
"forced": false,
|
||||
"commits": []
|
||||
}
|
||||
});
|
||||
let item = reshape(&ev("PushEvent", raw));
|
||||
assert_eq!(item.icon, TimelineIcon::GitBranchCreate);
|
||||
let r = render(&item);
|
||||
assert!(
|
||||
r.contains("created branch fix-double-panic in grenade/vortex"),
|
||||
"got: {r}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_push_with_commits_says_force_pushed() {
|
||||
let raw = json!({
|
||||
"actor": { "login": "grenade" },
|
||||
"repo": { "name": "grenade/x" },
|
||||
"payload": {
|
||||
"ref": "refs/heads/main",
|
||||
"size": 1,
|
||||
"distinct_size": 1,
|
||||
"created": false,
|
||||
"forced": true,
|
||||
"commits": [{ "sha": "deadbeefcafe1234", "message": "rebase" }]
|
||||
}
|
||||
});
|
||||
let item = reshape(&ev("PushEvent", raw));
|
||||
assert_eq!(item.icon, TimelineIcon::GitPush);
|
||||
let r = render(&item);
|
||||
assert!(r.contains("force-pushed 1 commit to grenade/x:main"), "got: {r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_push_omits_commit_count() {
|
||||
let raw = json!({
|
||||
"actor": { "login": "grenade" },
|
||||
"repo": { "name": "grenade/x" },
|
||||
"payload": {
|
||||
"ref": "refs/heads/main",
|
||||
"size": 0,
|
||||
"distinct_size": 0,
|
||||
"created": false,
|
||||
"forced": false,
|
||||
"commits": []
|
||||
}
|
||||
});
|
||||
let item = reshape(&ev("PushEvent", raw));
|
||||
assert_eq!(item.icon, TimelineIcon::GitPush);
|
||||
let r = render(&item);
|
||||
assert!(r.contains("pushed to grenade/x:main"), "got: {r}");
|
||||
assert!(!r.contains("0 commit"), "should not say '0 commits': {r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merged_pr_uses_merge_icon() {
|
||||
let raw = json!({
|
||||
|
||||
Reference in New Issue
Block a user