Skip to content

Commit 8cbf997

Browse files
authored
Allow for force pushing for existing branches (#9)
* fetch target branch ref info in initial query * Allow for force pushing when branch doesn't match expectations
1 parent 1ee8ba4 commit 8cbf997

File tree

5 files changed

+252
-59
lines changed

5 files changed

+252
-59
lines changed

src/core.ts

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
createRefMutation,
88
getRepositoryMetadata,
99
GitHubClient,
10+
updateRefMutation,
1011
} from "./github/graphql/queries.js";
1112
import type {
1213
CreateCommitOnBranchMutationVariables,
@@ -38,6 +39,11 @@ export type CommitFilesFromBase64Args = {
3839
* The current branch, tag or commit that the new branch should be based on.
3940
*/
4041
base: GitBase;
42+
/**
43+
* Push the commit even if the branch exists and does not match what was
44+
* specified as the base.
45+
*/
46+
force?: boolean;
4147
/**
4248
* The commit message
4349
*/
@@ -58,7 +64,8 @@ const getBaseRef = (base: GitBase): string => {
5864

5965
const getOidFromRef = (
6066
base: GitBase,
61-
ref: (GetRepositoryMetadataQuery["repository"] & Record<never, never>)["ref"],
67+
ref: (GetRepositoryMetadataQuery["repository"] &
68+
Record<never, never>)["baseRef"],
6269
) => {
6370
if ("commit" in base) {
6471
return base.commit;
@@ -81,26 +88,29 @@ export const commitFilesFromBase64 = async ({
8188
repository,
8289
branch,
8390
base,
91+
force = false,
8492
message,
8593
fileChanges,
8694
log,
8795
}: CommitFilesFromBase64Args): Promise<CommitFilesResult> => {
8896
const repositoryNameWithOwner = `${owner}/${repository}`;
8997
const baseRef = getBaseRef(base);
98+
const targetRef = `refs/heads/${branch}`;
9099

91100
log?.debug(`Getting repo info ${repositoryNameWithOwner}`);
92101
const info = await getRepositoryMetadata(octokit, {
93102
owner,
94103
name: repository,
95-
ref: baseRef,
104+
baseRef,
105+
targetRef,
96106
});
97107
log?.debug(`Repo info: ${JSON.stringify(info, null, 2)}`);
98108

99109
if (!info) {
100110
throw new Error(`Repository ${repositoryNameWithOwner} not found`);
101111
}
102112

103-
if (!info.ref) {
113+
if (!info.baseRef) {
104114
throw new Error(`Ref ${baseRef} not found`);
105115
}
106116

@@ -109,39 +119,77 @@ export const commitFilesFromBase64 = async ({
109119
* The commit oid to base the new commit on.
110120
*
111121
* Used both to create / update the new branch (if necessary),
112-
* and th ensure no changes have been made as we push the new commit.
122+
* and to ensure no changes have been made as we push the new commit.
113123
*/
114-
const baseOid = getOidFromRef(base, info.ref);
124+
const baseOid = getOidFromRef(base, info.baseRef);
115125

116126
let refId: string;
117127

118128
if ("branch" in base && base.branch === branch) {
119129
log?.debug(`Committing to the same branch as base: ${branch} (${baseOid})`);
120130
// Get existing branch refId
121-
refId = info.ref.id;
131+
refId = info.baseRef.id;
122132
} else {
123-
// Create branch as not committing to same branch
124-
// TODO: detect if branch already exists, and overwrite if so
125-
log?.debug(`Creating branch ${branch} from commit ${baseOid}}`);
126-
const refIdCreation = await createRefMutation(octokit, {
127-
input: {
128-
repositoryId,
129-
name: `refs/heads/${branch}`,
130-
oid: baseOid,
131-
},
132-
});
133-
134-
log?.debug(
135-
`Created branch with refId ${JSON.stringify(refIdCreation, null, 2)}`,
136-
);
137-
138-
const refIdStr = refIdCreation.createRef?.ref?.id;
139-
140-
if (!refIdStr) {
141-
throw new Error(`Failed to create branch ${branch}`);
133+
// Determine if the branch needs to be created or not
134+
if (info.targetBranch?.target?.oid) {
135+
// Branch already exists, check if it matches the base
136+
if (info.targetBranch.target.oid !== baseOid) {
137+
if (force) {
138+
log?.debug(
139+
`Branch ${branch} exists but does not match base ${baseOid}, forcing update to base`,
140+
);
141+
const refIdUpdate = await updateRefMutation(octokit, {
142+
input: {
143+
refId: info.targetBranch.id,
144+
oid: baseOid,
145+
},
146+
});
147+
148+
log?.debug(
149+
`Updated branch with refId ${JSON.stringify(refIdUpdate, null, 2)}`,
150+
);
151+
152+
const refIdStr = refIdUpdate.updateRef?.ref?.id;
153+
154+
if (!refIdStr) {
155+
throw new Error(`Failed to create branch ${branch}`);
156+
}
157+
158+
refId = refIdStr;
159+
} else {
160+
throw new Error(
161+
`Branch ${branch} exists already and does not match base ${baseOid}, force is set to false`,
162+
);
163+
}
164+
} else {
165+
log?.debug(
166+
`Branch ${branch} already exists and matches base ${baseOid}`,
167+
);
168+
refId = info.targetBranch.id;
169+
}
170+
} else {
171+
// Create branch as it does not exist yet
172+
log?.debug(`Creating branch ${branch} from commit ${baseOid}}`);
173+
const refIdCreation = await createRefMutation(octokit, {
174+
input: {
175+
repositoryId,
176+
name: `refs/heads/${branch}`,
177+
oid: baseOid,
178+
},
179+
});
180+
181+
log?.debug(
182+
`Created branch with refId ${JSON.stringify(refIdCreation, null, 2)}`,
183+
);
184+
185+
const refIdStr = refIdCreation.createRef?.ref?.id;
186+
187+
if (!refIdStr) {
188+
throw new Error(`Failed to create branch ${branch}`);
189+
}
190+
191+
refId = refIdStr;
142192
}
143-
144-
refId = refIdStr;
145193
}
146194

147195
await log?.debug(`Creating commit on branch ${branch}`);

src/github/graphql/queries.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ import type {
1111
DeleteRefMutationVariables,
1212
GetRepositoryMetadataQuery,
1313
GetRepositoryMetadataQueryVariables,
14+
UpdateRefMutation,
15+
UpdateRefMutationVariables,
1416
} from "./generated/operations.js";
1517

1618
const GET_REPOSITORY_METADATA = /* GraphQL */ `
17-
query getRepositoryMetadata($owner: String!, $name: String!, $ref: String!) {
19+
query getRepositoryMetadata(
20+
$owner: String!
21+
$name: String!
22+
$baseRef: String!
23+
$targetRef: String!
24+
) {
1825
repository(owner: $owner, name: $name) {
1926
id
20-
ref(qualifiedName: $ref) {
27+
baseRef: ref(qualifiedName: $baseRef) {
2128
id
2229
target {
2330
oid
@@ -28,6 +35,12 @@ const GET_REPOSITORY_METADATA = /* GraphQL */ `
2835
}
2936
}
3037
}
38+
targetBranch: ref(qualifiedName: $targetRef) {
39+
id
40+
target {
41+
oid
42+
}
43+
}
3144
}
3245
}
3346
`;
@@ -42,6 +55,16 @@ const CREATE_REF = /* GraphQL */ `
4255
}
4356
`;
4457

58+
const UPDATE_REF = /* GraphQL */ `
59+
mutation updateRef($input: UpdateRefInput!) {
60+
updateRef(input: $input) {
61+
ref {
62+
id
63+
}
64+
}
65+
}
66+
`;
67+
4568
const DELETE_REF = /* GraphQL */ `
4669
mutation deleteRef($input: DeleteRefInput!) {
4770
deleteRef(input: $input) {
@@ -77,6 +100,12 @@ export const createRefMutation = async (
77100
): Promise<CreateRefMutation> =>
78101
await o.graphql<CreateRefMutation>(CREATE_REF, v);
79102

103+
export const updateRefMutation = async (
104+
o: GitHubClient,
105+
v: UpdateRefMutationVariables,
106+
): Promise<UpdateRefMutation> =>
107+
await o.graphql<UpdateRefMutation>(UPDATE_REF, v);
108+
80109
export const deleteRefMutation = async (
81110
o: GitHubClient,
82111
v: DeleteRefMutationVariables,

src/test/integration/jest.globalTeardown.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ module.exports = async (_: unknown, projectConfig: Config) => {
88
}
99
console.log(`Deleting directory: ${directory}`);
1010

11-
await fs.rm(directory, { recursive: true });
11+
await fs.rm(directory, { recursive: true }).catch((err) => {
12+
console.error(`Error deleting directory: ${err}`);
13+
});
1214
};

0 commit comments

Comments
 (0)