Skip to content

Commit d069320

Browse files
authored
feat: selective starred repo mirroring with autoMirrorStarred toggle (#208)
* feat: add autoMirrorStarred toggle for selective starred repo mirroring (#205) Add `githubConfig.autoMirrorStarred` (default: false) to control whether starred repos are included in automatic mirroring operations. Manual per-repo actions always work regardless of this toggle. Bug fixes: - Cleanup service no longer orphans starred repos when includeStarred is disabled (prevents data loss) - First-boot auto-start now gates initial mirror behind autoMirror config (previously mirrored everything unconditionally) - "Mirror All" button now respects autoMirrorStarred setting - Bulk mirror and getAvailableActions now include pending-approval status Changes span schema, config mapping, env loader, scheduler, cleanup service, UI settings toggle, and repository components. * fix: log activity when repos are auto-imported during scheduled sync Auto-discovered repositories (including newly starred ones) were inserted into the database without creating activity log entries, so they appeared in the dashboard but not in the activity log. * ci: set 10-minute timeout on all CI jobs
1 parent b079070 commit d069320

16 files changed

Lines changed: 152 additions & 13 deletions

.github/workflows/astro-build-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
build-and-test:
2525
name: Build and Test Astro Project
2626
runs-on: ubuntu-latest
27+
timeout-minutes: 10
2728

2829
steps:
2930
- name: Checkout repository

.github/workflows/docker-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ env:
3636
jobs:
3737
docker:
3838
runs-on: ubuntu-latest
39+
timeout-minutes: 10
3940

4041
permissions:
4142
contents: write

.github/workflows/e2e-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
e2e-tests:
4747
name: E2E Integration Tests
4848
runs-on: ubuntu-latest
49-
timeout-minutes: 25
49+
timeout-minutes: 10
5050

5151
steps:
5252
- name: Checkout repository

.github/workflows/helm-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
yamllint:
2222
name: Lint YAML
2323
runs-on: ubuntu-latest
24+
timeout-minutes: 10
2425
steps:
2526
- uses: actions/checkout@v4
2627
- uses: actions/setup-python@v5
@@ -35,6 +36,7 @@ jobs:
3536
helm-template:
3637
name: Helm lint & template
3738
runs-on: ubuntu-latest
39+
timeout-minutes: 10
3840
steps:
3941
- uses: actions/checkout@v4
4042
- name: Setup Helm

.github/workflows/nix-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ permissions:
2424
jobs:
2525
check:
2626
runs-on: ubuntu-latest
27+
timeout-minutes: 10
2728

2829
steps:
2930
- uses: actions/checkout@v4

docs/ENVIRONMENT_VARIABLES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Settings for connecting to and configuring GitHub repository sources.
7878
| Variable | Description | Default | Options |
7979
|----------|-------------|---------|---------|
8080
| `SKIP_STARRED_ISSUES` | Enable lightweight mode for starred repos (skip issues) | `false` | `true`, `false` |
81+
| `AUTO_MIRROR_STARRED` | Automatically mirror starred repos during scheduled syncs and "Mirror All". When `false`, starred repos are imported for browsing but must be mirrored individually. | `false` | `true`, `false` |
8182

8283
## Gitea Configuration
8384

src/components/config/ConfigTabs.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export function ConfigTabs() {
8383
advancedOptions: {
8484
skipForks: false,
8585
starredCodeOnly: false,
86+
autoMirrorStarred: false,
8687
},
8788
});
8889
const { user } = useAuth();

src/components/config/GitHubMirrorSettings.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,31 @@ export function GitHubMirrorSettings({
287287
</div>
288288
</div>
289289

290+
{/* Auto-mirror starred repos toggle */}
291+
{githubConfig.mirrorStarred && (
292+
<div className="mt-4">
293+
<div className="flex items-start space-x-3">
294+
<Checkbox
295+
id="auto-mirror-starred"
296+
checked={advancedOptions.autoMirrorStarred ?? false}
297+
onCheckedChange={(checked) => handleAdvancedChange('autoMirrorStarred', !!checked)}
298+
/>
299+
<div className="space-y-0.5 flex-1">
300+
<Label
301+
htmlFor="auto-mirror-starred"
302+
className="text-sm font-normal cursor-pointer flex items-center gap-2"
303+
>
304+
<Star className="h-3.5 w-3.5" />
305+
Auto-mirror new starred repositories
306+
</Label>
307+
<p className="text-xs text-muted-foreground">
308+
When disabled, starred repos are imported for browsing but not automatically mirrored. You can still mirror individual repos manually.
309+
</p>
310+
</div>
311+
</div>
312+
</div>
313+
)}
314+
290315
{/* Duplicate name handling for starred repos */}
291316
{githubConfig.mirrorStarred && (
292317
<div className="mt-4 space-y-2">

src/components/repositories/Repository.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default function Repository() {
5656
const [isInitialLoading, setIsInitialLoading] = useState(true);
5757
const { user } = useAuth();
5858
const { registerRefreshCallback, isLiveEnabled } = useLiveRefresh();
59-
const { isGitHubConfigured, isFullyConfigured } = useConfigStatus();
59+
const { isGitHubConfigured, isFullyConfigured, autoMirrorStarred, githubOwner } = useConfigStatus();
6060
const { navigationKey } = useNavigation();
6161
const { filter, setFilter } = useFilterParams({
6262
searchTerm: "",
@@ -233,10 +233,12 @@ export default function Repository() {
233233
// Filter out repositories that are already mirroring, mirrored, or ignored
234234
const eligibleRepos = repositories.filter(
235235
(repo) =>
236-
repo.status !== "mirroring" &&
237-
repo.status !== "mirrored" &&
236+
repo.status !== "mirroring" &&
237+
repo.status !== "mirrored" &&
238238
repo.status !== "ignored" && // Skip ignored repositories
239-
repo.id
239+
repo.id &&
240+
// Skip starred repos from other owners when autoMirrorStarred is disabled
241+
!(repo.isStarred && !autoMirrorStarred && repo.owner !== githubOwner)
240242
);
241243

242244
if (eligibleRepos.length === 0) {
@@ -292,7 +294,7 @@ export default function Repository() {
292294

293295
const selectedRepos = repositories.filter(repo => repo.id && selectedRepoIds.has(repo.id));
294296
const eligibleRepos = selectedRepos.filter(
295-
repo => repo.status === "imported" || repo.status === "failed"
297+
repo => repo.status === "imported" || repo.status === "failed" || repo.status === "pending-approval"
296298
);
297299

298300
if (eligibleRepos.length === 0) {
@@ -301,7 +303,7 @@ export default function Repository() {
301303
}
302304

303305
const repoIds = eligibleRepos.map(repo => repo.id as string);
304-
306+
305307
setLoadingRepoIds(prev => {
306308
const newSet = new Set(prev);
307309
repoIds.forEach(id => newSet.add(id));
@@ -937,7 +939,7 @@ export default function Repository() {
937939
const actions = [];
938940

939941
// Check if any selected repos can be mirrored
940-
if (selectedRepos.some(repo => repo.status === "imported" || repo.status === "failed")) {
942+
if (selectedRepos.some(repo => repo.status === "imported" || repo.status === "failed" || repo.status === "pending-approval")) {
941943
actions.push('mirror');
942944
}
943945

@@ -975,7 +977,7 @@ export default function Repository() {
975977
const selectedRepos = repositories.filter(repo => repo.id && selectedRepoIds.has(repo.id));
976978

977979
return {
978-
mirror: selectedRepos.filter(repo => repo.status === "imported" || repo.status === "failed").length,
980+
mirror: selectedRepos.filter(repo => repo.status === "imported" || repo.status === "failed" || repo.status === "pending-approval").length,
979981
sync: selectedRepos.filter(repo => repo.status === "mirrored" || repo.status === "synced").length,
980982
rerunMetadata: selectedRepos.filter(repo => ["mirrored", "synced", "archived"].includes(repo.status)).length,
981983
retry: selectedRepos.filter(repo => repo.status === "failed").length,

src/hooks/useConfigStatus.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ interface ConfigStatus {
99
isFullyConfigured: boolean;
1010
isLoading: boolean;
1111
error: string | null;
12+
autoMirrorStarred: boolean;
13+
githubOwner: string;
1214
}
1315

1416
// Cache to prevent duplicate API calls across components
@@ -33,6 +35,8 @@ export function useConfigStatus(): ConfigStatus {
3335
isFullyConfigured: false,
3436
isLoading: true,
3537
error: null,
38+
autoMirrorStarred: false,
39+
githubOwner: '',
3640
});
3741

3842
// Track if this hook has already checked config to prevent multiple calls
@@ -46,6 +50,8 @@ export function useConfigStatus(): ConfigStatus {
4650
isFullyConfigured: false,
4751
isLoading: false,
4852
error: 'No user found',
53+
autoMirrorStarred: false,
54+
githubOwner: '',
4955
});
5056
return;
5157
}
@@ -78,6 +84,8 @@ export function useConfigStatus(): ConfigStatus {
7884
isFullyConfigured,
7985
isLoading: false,
8086
error: null,
87+
autoMirrorStarred: configResponse?.advancedOptions?.autoMirrorStarred ?? false,
88+
githubOwner: configResponse?.githubConfig?.username ?? '',
8189
});
8290
return;
8391
}
@@ -119,6 +127,8 @@ export function useConfigStatus(): ConfigStatus {
119127
isFullyConfigured,
120128
isLoading: false,
121129
error: null,
130+
autoMirrorStarred: configResponse?.advancedOptions?.autoMirrorStarred ?? false,
131+
githubOwner: configResponse?.githubConfig?.username ?? '',
122132
});
123133

124134
hasCheckedRef.current = true;
@@ -129,6 +139,8 @@ export function useConfigStatus(): ConfigStatus {
129139
isFullyConfigured: false,
130140
isLoading: false,
131141
error: error instanceof Error ? error.message : 'Failed to check configuration',
142+
autoMirrorStarred: false,
143+
githubOwner: '',
132144
});
133145
hasCheckedRef.current = true;
134146
}

0 commit comments

Comments
 (0)