Skip to content

Commit d41c2cf

Browse files
Akrabutmheap
andauthored
Add filesToDelete option (#7)
Co-authored-by: Michael Heap <m@michaelheap.com>
1 parent 3fd0647 commit d41c2cf

File tree

3 files changed

+409
-113
lines changed

3 files changed

+409
-113
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,25 @@ In addition, you can set the `mode` of a file change. For example, if you wanted
6363
}
6464
}
6565
```
66+
67+
In addition, you can set the `filesToDelete` property as an array of strings (file paths) to set files for deletion in a single commit (works with updates and creations).
68+
69+
```javascript
70+
{
71+
"message": "This commit removes files",
72+
"filesToDelete": ['path/to/my/file.txt', 'path/to/another.js'],
73+
}
74+
```
75+
76+
- Note that the `ignoreDeletionFailures` property is set to false by default (works in a context of a single change).
77+
- If `ignoreDeletionFailures` is set to false, an error will be thrown if any file set for deletion is missing and the commits will stop processing. Any commits made before this will still be applied. Any changes in this `change` will not be committed. No future changes will be applied.
78+
- If `ignoreDeletionFailures` is set to true, missing files that are set for deletion will be ignored.
79+
- If a file is created and deleted in the same `change`, the file will be created/updated
80+
81+
```javascript
82+
{
83+
"message": "This commit removes files",
84+
"filesToDelete": ['path/to/my/file.txt', 'path/to/another.js'],
85+
"ignoreDeletionFailures": true,
86+
}
87+
```

create-or-update-files.js

Lines changed: 146 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module.exports = function(octokit, opts) {
22
return new Promise(async (resolve, reject) => {
33
// Up front validation
44
try {
5-
for (let req of ["owner", "repo", "branch"]) {
5+
for (const req of ["owner", "repo", "branch"]) {
66
if (!opts[req]) {
77
return reject(`'${req}' is a required parameter`);
88
}
@@ -19,7 +19,7 @@ module.exports = function(octokit, opts) {
1919
base,
2020
branch: branchName,
2121
createBranch,
22-
changes
22+
changes,
2323
} = opts;
2424

2525
let branchAlreadyExists = true;
@@ -43,7 +43,7 @@ module.exports = function(octokit, opts) {
4343
base = (
4444
await octokit.repos.get({
4545
owner,
46-
repo
46+
repo,
4747
})
4848
).data.default_branch;
4949
}
@@ -56,70 +56,106 @@ module.exports = function(octokit, opts) {
5656
}
5757

5858
// Create blobs
59-
for (let change of changes) {
60-
let message = change.message;
59+
for (const change of changes) {
60+
const message = change.message;
6161
if (!message) {
6262
return reject(`changes[].message is a required parameter`);
6363
}
64-
if (!change.files || Object.keys(change.files).length === 0) {
65-
return reject(`changes[].files is a required parameter`);
64+
65+
const hasFiles = change.files && Object.keys(change.files).length > 0;
66+
67+
const hasFilesToDelete =
68+
Array.isArray(change.filesToDelete) &&
69+
change.filesToDelete.length > 0;
70+
71+
if (!hasFiles && !hasFilesToDelete) {
72+
return reject(
73+
`either changes[].files or changes[].filesToDelete are required`
74+
);
6675
}
6776

6877
const treeItems = [];
69-
for (let fileName in change.files) {
70-
let properties = change.files[fileName] || "";
78+
// Handle file deletions
79+
if (hasFilesToDelete) {
80+
for (const fileName of change.filesToDelete) {
81+
const exists = await fileExistsInRepo(
82+
octokit,
83+
owner,
84+
repo,
85+
fileName,
86+
baseTree
87+
);
88+
89+
// If it doesn't exist, and we're not ignoring missing files
90+
// reject the promise
91+
if (!exists && !change.ignoreDeletionFailures) {
92+
return reject(
93+
`The file ${fileName} could not be found in the repo`
94+
);
95+
}
96+
97+
// At this point it either exists, or we're ignoring failures
98+
if (exists) {
99+
treeItems.push({
100+
path: fileName,
101+
sha: null, // sha as null implies that the file should be deleted
102+
mode: "100644",
103+
type: "commit",
104+
});
105+
}
106+
}
107+
}
108+
109+
for (const fileName in change.files) {
110+
const properties = change.files[fileName] || "";
71111

72-
let contents = properties.contents || properties;
73-
let mode = properties.mode || "100644";
74-
let type = properties.type || "blob";
112+
const contents = properties.contents || properties;
113+
const mode = properties.mode || "100644";
114+
const type = properties.type || "blob";
75115

76116
if (!contents) {
77117
return reject(`No file contents provided for ${fileName}`);
78118
}
79119

80-
let fileSha;
81-
if (type == "commit") {
82-
fileSha = contents;
83-
} else {
84-
let file = (
85-
await octokit.git.createBlob({
86-
owner,
87-
repo,
88-
content: Buffer.from(contents).toString("base64"),
89-
encoding: "base64"
90-
})
91-
).data;
92-
fileSha = file.sha;
93-
}
120+
const fileSha = await createBlob(
121+
octokit,
122+
owner,
123+
repo,
124+
contents,
125+
type
126+
);
94127

95128
treeItems.push({
96129
path: fileName,
97130
sha: fileSha,
98131
mode: mode,
99-
type: type
132+
type: type,
100133
});
101134
}
102135

136+
// no need to issue further requests if there are no updates, creations and deletions
137+
if (treeItems.length === 0) {
138+
continue;
139+
}
140+
103141
// Add those blobs to a tree
104-
let tree = (
105-
await octokit.git.createTree({
106-
owner,
107-
repo,
108-
tree: treeItems,
109-
base_tree: baseTree
110-
})
111-
).data;
142+
const tree = await createTree(
143+
octokit,
144+
owner,
145+
repo,
146+
treeItems,
147+
baseTree
148+
);
112149

113150
// Create a commit that points to that tree
114-
let commit = (
115-
await octokit.git.createCommit({
116-
owner,
117-
repo,
118-
message,
119-
tree: tree.sha,
120-
parents: [baseTree]
121-
})
122-
).data;
151+
const commit = await createCommit(
152+
octokit,
153+
owner,
154+
repo,
155+
message,
156+
tree,
157+
baseTree
158+
);
123159

124160
// Update the base tree if we have another commit to make
125161
baseTree = commit.sha;
@@ -135,15 +171,13 @@ module.exports = function(octokit, opts) {
135171
updateRefBase = "";
136172
}
137173

138-
const branch = (
139-
await octokit.git[action]({
140-
owner,
141-
repo,
142-
force: true,
143-
ref: `${updateRefBase}heads/${branchName}`,
144-
sha: baseTree
145-
})
146-
).data;
174+
await octokit.git[action]({
175+
owner,
176+
repo,
177+
force: true,
178+
ref: `${updateRefBase}heads/${branchName}`,
179+
sha: baseTree,
180+
});
147181

148182
// Return the new branch name so that we can use it later
149183
// e.g. to create a pull request
@@ -154,16 +188,69 @@ module.exports = function(octokit, opts) {
154188
});
155189
};
156190

157-
async function loadRef(octokit, owner, repo, ref) {
191+
async function fileExistsInRepo(octokit, owner, repo, path, branch) {
158192
try {
159-
return (
160-
await octokit.git.getRef({
193+
await octokit.repos.getContent({
194+
method: "HEAD",
195+
owner,
196+
repo,
197+
path,
198+
ref: branch,
199+
});
200+
return true;
201+
} catch (e) {
202+
return false;
203+
}
204+
}
205+
206+
async function createCommit(octokit, owner, repo, message, tree, baseTree) {
207+
return (
208+
await octokit.git.createCommit({
209+
owner,
210+
repo,
211+
message,
212+
tree: tree.sha,
213+
parents: [baseTree],
214+
})
215+
).data;
216+
}
217+
218+
async function createTree(octokit, owner, repo, treeItems, baseTree) {
219+
return (
220+
await octokit.git.createTree({
221+
owner,
222+
repo,
223+
tree: treeItems,
224+
base_tree: baseTree,
225+
})
226+
).data;
227+
}
228+
229+
async function createBlob(octokit, owner, repo, contents, type) {
230+
if (type === "commit") {
231+
return contents;
232+
} else {
233+
const file = (
234+
await octokit.git.createBlob({
161235
owner,
162236
repo,
163-
ref: `heads/${ref}`
237+
content: Buffer.from(contents).toString("base64"),
238+
encoding: "base64",
164239
})
165-
).data.object.sha;
240+
).data;
241+
return file.sha;
242+
}
243+
}
244+
245+
async function loadRef(octokit, owner, repo, ref) {
246+
try {
247+
const x = await octokit.git.getRef({
248+
owner,
249+
repo,
250+
ref: `heads/${ref}`,
251+
});
252+
return x.data.object.sha;
166253
} catch (e) {
167-
//console.log(e);
254+
// console.log(e);
168255
}
169256
}

0 commit comments

Comments
 (0)