feat(sync): add parallel sync, fix default branch and protected branch issues, add group avatars option

- Set sync concurrency to 10 for faster parallel repo syncing
- Three-phase push: push refs, sync default_branch via API, then push with --prune
- Unprotect stale protected branches on target before pruning
- Handle group visibility 400 errors gracefully (skip visibility, sync description only)
- Add useGroupAvatarsForProjects option: projects without avatars inherit group avatar
- Upgrade @apiclient.xyz/gitlab to v2.3.0 (getProtectedBranches, unprotectBranch)
This commit is contained in:
2026-02-28 17:39:28 +00:00
parent c9a758b417
commit 44ac2e430f
8 changed files with 148 additions and 10 deletions

View File

@@ -742,6 +742,7 @@ export const createSyncConfigAction = syncStatePart.createAction<{
enforceDelete?: boolean;
enforceGroupDelete?: boolean;
addMirrorHint?: boolean;
useGroupAvatarsForProjects?: boolean;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
@@ -769,6 +770,7 @@ export const updateSyncConfigAction = syncStatePart.createAction<{
enforceDelete?: boolean;
enforceGroupDelete?: boolean;
addMirrorHint?: boolean;
useGroupAvatarsForProjects?: boolean;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {

View File

@@ -104,6 +104,7 @@ export class GitopsViewSync extends DeesElement {
'Enforce Delete': item.enforceDelete ? 'Yes' : 'No',
'Enforce Group Delete': item.enforceGroupDelete ? 'Yes' : 'No',
'Mirror Hint': item.addMirrorHint ? 'Yes' : 'No',
'Group Avatars': item.useGroupAvatarsForProjects ? 'Yes' : 'No',
'Last Sync': item.lastSyncAt ? new Date(item.lastSyncAt).toLocaleString() : 'Never',
Repos: String(item.reposSynced),
};
@@ -288,6 +289,9 @@ export class GitopsViewSync extends DeesElement {
<div class="form-row">
<dees-input-checkbox .label=${'Add Mirror Hint'} .key=${'addMirrorHint'} .value=${false} .description=${'When enabled, target descriptions get "(This is a mirror of ...)" appended.'}></dees-input-checkbox>
</div>
<div class="form-row">
<dees-input-checkbox .label=${'Group Avatars for Projects'} .key=${'useGroupAvatarsForProjects'} .value=${false} .description=${'When enabled, projects without their own avatar inherit the group avatar.'}></dees-input-checkbox>
</div>
`,
menuOptions: [
{ name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
@@ -299,7 +303,7 @@ export class GitopsViewSync extends DeesElement {
for (const input of inputs) {
if (input.key === 'sourceConnectionId' || input.key === 'targetConnectionId') {
data[input.key] = input.selectedOption?.key || '';
} else if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint') {
} else if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint' || input.key === 'useGroupAvatarsForProjects') {
data[input.key] = input.getValue();
} else {
data[input.key] = input.value || '';
@@ -314,6 +318,7 @@ export class GitopsViewSync extends DeesElement {
enforceDelete: !!data.enforceDelete,
enforceGroupDelete: !!data.enforceGroupDelete,
addMirrorHint: !!data.addMirrorHint,
useGroupAvatarsForProjects: !!data.useGroupAvatarsForProjects,
});
modal.destroy();
},
@@ -345,6 +350,9 @@ export class GitopsViewSync extends DeesElement {
<div class="form-row">
<dees-input-checkbox .label=${'Add Mirror Hint'} .key=${'addMirrorHint'} .value=${!!item.addMirrorHint} .description=${'When enabled, target descriptions get "(This is a mirror of ...)" appended.'}></dees-input-checkbox>
</div>
<div class="form-row">
<dees-input-checkbox .label=${'Group Avatars for Projects'} .key=${'useGroupAvatarsForProjects'} .value=${!!item.useGroupAvatarsForProjects} .description=${'When enabled, projects without their own avatar inherit the group avatar.'}></dees-input-checkbox>
</div>
`,
menuOptions: [
{ name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
@@ -354,7 +362,7 @@ export class GitopsViewSync extends DeesElement {
const inputs = modal.shadowRoot.querySelectorAll('dees-input-text, dees-input-checkbox');
const data: any = {};
for (const input of inputs) {
if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint') {
if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint' || input.key === 'useGroupAvatarsForProjects') {
data[input.key] = input.getValue();
} else {
data[input.key] = input.value || '';
@@ -368,6 +376,7 @@ export class GitopsViewSync extends DeesElement {
enforceDelete: !!data.enforceDelete,
enforceGroupDelete: !!data.enforceGroupDelete,
addMirrorHint: !!data.addMirrorHint,
useGroupAvatarsForProjects: !!data.useGroupAvatarsForProjects,
});
modal.destroy();
},