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 {
|
fn push(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
|
||||||
let repo = repo.unwrap_or("(unknown repo)");
|
let repo = repo.unwrap_or("(unknown repo)");
|
||||||
let size = p
|
let distinct_size = p
|
||||||
.and_then(|v| v.get("distinct_size").or_else(|| v.get("size")))
|
.and_then(|v| v.get("distinct_size"))
|
||||||
.and_then(Value::as_i64)
|
.and_then(Value::as_i64)
|
||||||
.unwrap_or(0);
|
.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
|
let branch = p
|
||||||
.and_then(|v| v.get("ref"))
|
.and_then(|v| v.get("ref"))
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.map(ref_branch)
|
.map(ref_branch)
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
let title = vec![
|
// Branch-creation pushes have distinct_size = 0 (the commits already
|
||||||
TitleSegment::text(format!(
|
// existed on another branch) and a different intent than a code push.
|
||||||
"pushed {size} commit{} to ",
|
// Force-pushes and ordinary pushes both render with the GitPush icon
|
||||||
if size == 1 { "" } else { "s" }
|
// 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),
|
repo_link(repo),
|
||||||
TitleSegment::text(format!(":{branch}")),
|
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
|
let commits: Vec<CommitSummary> = p
|
||||||
.and_then(|v| v.get("commits"))
|
.and_then(|v| v.get("commits"))
|
||||||
@@ -148,7 +182,7 @@ fn push(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
|
|||||||
Some(TimelineBody::Commits { commits })
|
Some(TimelineBody::Commits { commits })
|
||||||
};
|
};
|
||||||
|
|
||||||
(TimelineIcon::GitPush, title, None, body)
|
(icon, title, None, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pull_request(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
|
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]
|
#[test]
|
||||||
fn merged_pr_uses_merge_icon() {
|
fn merged_pr_uses_merge_icon() {
|
||||||
let raw = json!({
|
let raw = json!({
|
||||||
|
|||||||
@@ -18,13 +18,10 @@ const RANGE_MIN = new Date('2010-01-01T00:00:00Z').getTime();
|
|||||||
const RANGE_MAX = Date.now();
|
const RANGE_MAX = Date.now();
|
||||||
|
|
||||||
const externalLinks: { url: string; alt: string }[] = [
|
const externalLinks: { url: string; alt: string }[] = [
|
||||||
{ url: 'https://instagram.com/rob_thij', alt: 'instagram' },
|
|
||||||
{ url: 'https://www.facebook.com/rob.thijssen', alt: 'facebook' },
|
|
||||||
{ url: 'https://linkedin.com/in/thijssen/', alt: 'linkedin' },
|
{ url: 'https://linkedin.com/in/thijssen/', alt: 'linkedin' },
|
||||||
{ url: 'https://stackoverflow.com/users/68115/grenade', alt: 'stackoverflow' },
|
{ url: 'https://stackoverflow.com/users/68115/grenade', alt: 'stackoverflow' },
|
||||||
{ url: 'https://github.com/grenade', alt: 'github' },
|
{ url: 'https://github.com/grenade', alt: 'github' },
|
||||||
{ url: 'https://git.lair.cafe/grenade', alt: 'gitea' },
|
{ url: 'https://git.lair.cafe/grenade', alt: 'gitea' },
|
||||||
{ url: 'https://steelhorseadventures.com', alt: 'steel horse adventures' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|||||||
Reference in New Issue
Block a user