Skip to content

Commit bbffff8

Browse files
committed
ci: sync the screenpipe fork against upstream on a schedule
Rebases AnkushMalaker/screenpipe's custom branch onto upstream screenpipe/screenpipe main daily, pushing with --force-with-lease. On conflict it aborts, leaves the fork untouched, and files (or comments on) a single issue here rather than opening a new one every night. Hosted in this repo rather than in the fork for two reasons: `schedule` only fires from a repo's default branch, so putting it in the fork would permanently diverge that fork's main from upstream; and GitHub disables scheduled workflows in forked repositories, which would stop the sync silently. Needs a SCREENPIPE_FORK_TOKEN secret — a fine-grained PAT on AnkushMalaker/screenpipe with Contents: read/write *and* Workflows: read/write. The latter is required because rebasing onto upstream routinely carries changes to .github/workflows/**, which GitHub refuses to accept from a token without workflow permission.
1 parent 6c74ea9 commit bbffff8

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: Sync screenpipe fork
2+
3+
# Keeps AnkushMalaker/screenpipe's custom branch rebased on upstream
4+
# screenpipe/screenpipe main.
5+
#
6+
# This lives in chronicle rather than in the fork itself for two reasons:
7+
# `schedule` only fires from a repo's default branch, so hosting it in the fork
8+
# would permanently diverge that fork's main from upstream; and GitHub disables
9+
# scheduled workflows in forked repositories, which would stop the sync
10+
# silently.
11+
12+
on:
13+
schedule:
14+
# 04:00 UTC daily — well clear of the local build window.
15+
- cron: "0 4 * * *"
16+
workflow_dispatch:
17+
inputs:
18+
push:
19+
# Compared against the string 'false' as well as boolean false at the
20+
# use site: `gh workflow run -f push=false` sends the *string* "false",
21+
# and a non-empty string is truthy in GitHub expressions, so a naive
22+
# `if: inputs.push` would force-push when asked not to. Use -F (capital)
23+
# to send a real boolean, but the guard does not rely on that.
24+
description: "Push the rebased branch to the fork"
25+
type: boolean
26+
default: true
27+
28+
permissions:
29+
contents: read
30+
issues: write
31+
32+
env:
33+
FORK: AnkushMalaker/screenpipe
34+
UPSTREAM: https://github.com/screenpipe/screenpipe
35+
BRANCH: custom/linux-timeline-stability
36+
37+
jobs:
38+
rebase:
39+
name: Rebase fork onto upstream main
40+
runs-on: ubuntu-latest
41+
steps:
42+
- name: Check out the fork's custom branch
43+
uses: actions/checkout@v4
44+
with:
45+
repository: ${{ env.FORK }}
46+
ref: ${{ env.BRANCH }}
47+
# Full history: a rebase needs the merge base with upstream, which a
48+
# shallow clone does not have.
49+
fetch-depth: 0
50+
token: ${{ secrets.SCREENPIPE_FORK_TOKEN }}
51+
persist-credentials: true
52+
53+
- name: Fetch upstream main
54+
run: |
55+
git remote add upstream "$UPSTREAM"
56+
# Only main: upstream carries hundreds of agent/* branches.
57+
git fetch --no-tags upstream main
58+
59+
- name: Report divergence
60+
id: diverge
61+
run: |
62+
base=$(git merge-base HEAD upstream/main)
63+
behind=$(git rev-list --count "$base"..upstream/main)
64+
ours=$(git rev-list --count "$base"..HEAD)
65+
echo "behind=$behind" >>"$GITHUB_OUTPUT"
66+
echo "ours=$ours" >>"$GITHUB_OUTPUT"
67+
echo "$behind new upstream commit(s); $ours local commit(s) on top"
68+
69+
- name: Rebase
70+
id: rebase
71+
if: steps.diverge.outputs.behind != '0'
72+
run: |
73+
git config user.name "github-actions[bot]"
74+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
75+
if git rebase upstream/main; then
76+
echo "clean=true" >>"$GITHUB_OUTPUT"
77+
else
78+
git rebase --abort || true
79+
echo "clean=false" >>"$GITHUB_OUTPUT"
80+
echo "::warning::rebase hit conflicts; leaving the fork untouched"
81+
fi
82+
83+
- name: Push rebased branch
84+
if: |
85+
steps.rebase.outputs.clean == 'true' &&
86+
(github.event_name == 'schedule' ||
87+
(inputs.push && inputs.push != 'false'))
88+
run: |
89+
# --force-with-lease, never plain --force: the rebase rewrote history,
90+
# but this still refuses if the branch moved since checkout (e.g. a
91+
# local push landed while this job was running).
92+
git push --force-with-lease origin "HEAD:$BRANCH"
93+
94+
- name: Open an issue on conflict
95+
if: steps.rebase.outputs.clean == 'false'
96+
env:
97+
GH_TOKEN: ${{ github.token }}
98+
BEHIND: ${{ steps.diverge.outputs.behind }}
99+
run: |
100+
title="screenpipe fork: upstream rebase needs manual conflict resolution"
101+
# Reuse one open issue rather than filing a new one every night.
102+
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" \
103+
--state open --search "$title in:title" --json number --jq '.[0].number')
104+
body=$(printf '%s\n' \
105+
"\`$BRANCH\` no longer rebases cleanly onto upstream main ($BEHIND commits behind)." \
106+
"" \
107+
"The fork was left untouched. Resolve it locally:" \
108+
"" \
109+
'```' \
110+
"cd untracked/screenpipe && ./sync-fork.sh" \
111+
"# fix conflicts, then: git add <files> && git rebase --continue" \
112+
"./sync-fork.sh --push" \
113+
'```' \
114+
"" \
115+
"Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID")
116+
if [ -n "$existing" ]; then
117+
gh issue comment "$existing" --repo "$GITHUB_REPOSITORY" --body "$body"
118+
else
119+
gh issue create --repo "$GITHUB_REPOSITORY" --title "$title" --body "$body"
120+
fi

0 commit comments

Comments
 (0)