fix(sync): use stable dedup key and skip threads when head unresolved
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
listPullRequests,
|
listPullRequests,
|
||||||
syncForkBranch,
|
syncForkBranch,
|
||||||
} from './githubClient';
|
} from './githubClient';
|
||||||
|
import { maintenanceDedupKey } from './maintenanceDedup';
|
||||||
import { shouldAutoSync, shouldCreateReviewThread } from './syncGating';
|
import { shouldAutoSync, shouldCreateReviewThread } from './syncGating';
|
||||||
|
|
||||||
const getRequiredUserId = async (ctx: ActionCtx) => {
|
const getRequiredUserId = async (ctx: ActionCtx) => {
|
||||||
@@ -244,6 +245,32 @@ const refreshOwnedSpoon = async (
|
|||||||
} catch (syncError) {
|
} catch (syncError) {
|
||||||
const message =
|
const message =
|
||||||
syncError instanceof Error ? syncError.message : String(syncError);
|
syncError instanceof Error ? syncError.message : String(syncError);
|
||||||
|
const dedupKey = maintenanceDedupKey({
|
||||||
|
mergeBaseSha: upstreamCompare.mergeBaseSha ?? forkCompare.mergeBaseSha,
|
||||||
|
headSha: upstreamCompare.headSha,
|
||||||
|
});
|
||||||
|
if (!dedupKey) {
|
||||||
|
console.warn(
|
||||||
|
'Skipping maintenance thread: upstream head SHA unresolvable for spoon',
|
||||||
|
spoonId,
|
||||||
|
);
|
||||||
|
await ctx.runMutation(internal.syncRuns.patchInternal, {
|
||||||
|
syncRunId,
|
||||||
|
status: 'failed',
|
||||||
|
error: `${message} (skipped maintenance thread: could not resolve upstream head SHA)`,
|
||||||
|
});
|
||||||
|
await ctx.runMutation(internal.spoons.patchSyncFields, {
|
||||||
|
spoonId,
|
||||||
|
syncStatus: 'conflict',
|
||||||
|
lastError: message,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: 'unknown' as const,
|
||||||
|
upstreamAheadBy: upstreamCompare.aheadBy,
|
||||||
|
forkAheadBy: forkCompare.aheadBy,
|
||||||
|
};
|
||||||
|
}
|
||||||
const threadId = await ctx.runMutation(
|
const threadId = await ctx.runMutation(
|
||||||
internal.threads.createMaintenanceThread,
|
internal.threads.createMaintenanceThread,
|
||||||
{
|
{
|
||||||
@@ -253,7 +280,8 @@ const refreshOwnedSpoon = async (
|
|||||||
title: `Resolve upstream sync conflict for ${spoon.name}`,
|
title: `Resolve upstream sync conflict for ${spoon.name}`,
|
||||||
summary: `GitHub refused the automatic upstream sync: ${message}`,
|
summary: `GitHub refused the automatic upstream sync: ${message}`,
|
||||||
upstreamFrom: upstreamCompare.mergeBaseSha,
|
upstreamFrom: upstreamCompare.mergeBaseSha,
|
||||||
upstreamTo: upstreamCompare.headSha ?? `${Date.now()}`,
|
upstreamTo: upstreamCompare.headSha,
|
||||||
|
dedupKey,
|
||||||
forkHeadAtCreation: forkCompare.headSha,
|
forkHeadAtCreation: forkCompare.headSha,
|
||||||
mergeBaseAtCreation:
|
mergeBaseAtCreation:
|
||||||
upstreamCompare.mergeBaseSha ?? forkCompare.mergeBaseSha,
|
upstreamCompare.mergeBaseSha ?? forkCompare.mergeBaseSha,
|
||||||
@@ -283,6 +311,22 @@ const refreshOwnedSpoon = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCreateReviewThread({ autoReviewEnabled }, { status })) {
|
if (shouldCreateReviewThread({ autoReviewEnabled }, { status })) {
|
||||||
|
const dedupKey = maintenanceDedupKey({
|
||||||
|
mergeBaseSha: upstreamCompare.mergeBaseSha ?? forkCompare.mergeBaseSha,
|
||||||
|
headSha: upstreamCompare.headSha,
|
||||||
|
});
|
||||||
|
if (!dedupKey) {
|
||||||
|
console.warn(
|
||||||
|
'Skipping maintenance thread: upstream head SHA unresolvable for spoon',
|
||||||
|
spoonId,
|
||||||
|
);
|
||||||
|
await ctx.runMutation(internal.syncRuns.patchInternal, {
|
||||||
|
syncRunId,
|
||||||
|
status: 'failed',
|
||||||
|
error:
|
||||||
|
'Could not resolve upstream head SHA; skipping maintenance thread.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
const threadId = await ctx.runMutation(
|
const threadId = await ctx.runMutation(
|
||||||
internal.threads.createMaintenanceThread,
|
internal.threads.createMaintenanceThread,
|
||||||
{
|
{
|
||||||
@@ -292,7 +336,8 @@ const refreshOwnedSpoon = async (
|
|||||||
title: `Review upstream changes for ${spoon.name}`,
|
title: `Review upstream changes for ${spoon.name}`,
|
||||||
summary: `Upstream has ${upstreamCompare.aheadBy} commit(s) and the fork has ${forkCompare.aheadBy} custom commit(s). Review whether upstream should be merged, ignored, or resolved in a draft PR.`,
|
summary: `Upstream has ${upstreamCompare.aheadBy} commit(s) and the fork has ${forkCompare.aheadBy} custom commit(s). Review whether upstream should be merged, ignored, or resolved in a draft PR.`,
|
||||||
upstreamFrom: upstreamCompare.mergeBaseSha,
|
upstreamFrom: upstreamCompare.mergeBaseSha,
|
||||||
upstreamTo: upstreamCompare.headSha ?? `${Date.now()}`,
|
upstreamTo: upstreamCompare.headSha,
|
||||||
|
dedupKey,
|
||||||
forkHeadAtCreation: forkCompare.headSha,
|
forkHeadAtCreation: forkCompare.headSha,
|
||||||
mergeBaseAtCreation:
|
mergeBaseAtCreation:
|
||||||
upstreamCompare.mergeBaseSha ?? forkCompare.mergeBaseSha,
|
upstreamCompare.mergeBaseSha ?? forkCompare.mergeBaseSha,
|
||||||
@@ -306,6 +351,7 @@ const refreshOwnedSpoon = async (
|
|||||||
decision: 'thread_created',
|
decision: 'thread_created',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status,
|
status,
|
||||||
@@ -408,6 +454,16 @@ export const syncForkWithUpstream = action({
|
|||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
const conflict = message.toLowerCase().includes('conflict');
|
const conflict = message.toLowerCase().includes('conflict');
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
|
const dedupKey = maintenanceDedupKey({
|
||||||
|
mergeBaseSha: state.mergeBaseSha ?? spoon.lastMergeBaseCommit,
|
||||||
|
headSha: state.upstreamHeadSha ?? spoon.lastUpstreamCommit,
|
||||||
|
});
|
||||||
|
if (!dedupKey) {
|
||||||
|
console.warn(
|
||||||
|
'Skipping maintenance thread: upstream head SHA unresolvable for spoon',
|
||||||
|
spoonId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
const threadId = await ctx.runMutation(
|
const threadId = await ctx.runMutation(
|
||||||
internal.threads.createMaintenanceThread,
|
internal.threads.createMaintenanceThread,
|
||||||
{
|
{
|
||||||
@@ -416,10 +472,8 @@ export const syncForkWithUpstream = action({
|
|||||||
source: 'merge_conflict',
|
source: 'merge_conflict',
|
||||||
title: `Resolve upstream sync conflict for ${spoon.name}`,
|
title: `Resolve upstream sync conflict for ${spoon.name}`,
|
||||||
summary: `GitHub reported a conflict while syncing upstream into this fork: ${message}`,
|
summary: `GitHub reported a conflict while syncing upstream into this fork: ${message}`,
|
||||||
upstreamTo:
|
upstreamTo: state.upstreamHeadSha ?? spoon.lastUpstreamCommit,
|
||||||
state.upstreamHeadSha ??
|
dedupKey,
|
||||||
spoon.lastUpstreamCommit ??
|
|
||||||
`${Date.now()}`,
|
|
||||||
forkHeadAtCreation: state.forkHeadSha ?? spoon.lastForkCommit,
|
forkHeadAtCreation: state.forkHeadSha ?? spoon.lastForkCommit,
|
||||||
mergeBaseAtCreation:
|
mergeBaseAtCreation:
|
||||||
state.mergeBaseSha ?? spoon.lastMergeBaseCommit,
|
state.mergeBaseSha ?? spoon.lastMergeBaseCommit,
|
||||||
@@ -433,6 +487,7 @@ export const syncForkWithUpstream = action({
|
|||||||
decision: 'thread_created',
|
decision: 'thread_created',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
await ctx.runMutation(internal.syncRuns.patchInternal, {
|
await ctx.runMutation(internal.syncRuns.patchInternal, {
|
||||||
syncRunId,
|
syncRunId,
|
||||||
status: conflict ? 'conflict' : 'failed',
|
status: conflict ? 'conflict' : 'failed',
|
||||||
|
|||||||
Reference in New Issue
Block a user