Skip to content

Commit

Permalink
feat: add support for binary files (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelastic committed Jun 3, 2020
1 parent d3d1da8 commit e71fac1
Show file tree
Hide file tree
Showing 5 changed files with 1,139 additions and 55 deletions.
83 changes: 46 additions & 37 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,65 @@
Please note that this project is released with a [Contributor Code of Conduct][coc].
By participating in this project you agree to abide by its terms.

## Running tests

All tests can be run with `npm test`. To run a single test, you can execute the test files directly with node, e.g.

```
node test/happy-path-test.js
```

or with the `tap` binary for nicer output.

```
npx tap test/happy-path-test.js
```

## Update test fixtures

Here is a script that records fixtures and logs them to stdout
Here is a script that records fixtures and logs them to stdout. Run with `GITHUB_TOKEN=... node my-script.js`. [Create token with repo scope](https://github.com/settings/tokens/new?scopes=repo)

```js
const TOKEN = process.env.TOKEN
const { Octokit } = require('@octokit/core')
const createPullRequest = require('.')
const MyOctokit = Octokit.plugin(createPullRequest)()
// my-script.js
const { Octokit } = require("@octokit/core");
const createPullRequest = require(".");
const MyOctokit = Octokit.plugin(createPullRequest);
const octokit = new MyOctokit({
auth: TOKEN
})
auth: process.env.GITHUB_TOKEN,
});


const fixtures = []
octokit.hook.after('request', (response, options) => {
const fixtures = [];
octokit.hook.after("request", (response, options) => {
fixtures.push({
request: options,
response
})
})

octokit.authenticate({
type: 'token',
token: process.env.GITHUB_TOKEN
})

octokit.createPullRequest({
owner: 'gr2m',
repo: 'pull-request-test',
title: 'One comes, one goes',
body: 'because',
head: 'test-branch-' + Math.random().toString(36).substr(2, 5),
changes: {
files: {
'path/to/file1.txt': 'Content for file1',
'path/to/file2.txt': 'Content for file2'
response,
});
});

octokit
.createPullRequest({
owner: "gr2m",
repo: "sandbox",
title: "One comes, one goes",
body: "because",
head: "test-branch-" + Math.random().toString(36).substr(2, 5),
changes: {
files: {
"path/to/file1.txt": "Content for file1",
"path/to/file2.txt": "Content for file2",
},
commit: "why",
},
commit: 'why'
}
})
})

.then(() => {
fixtures.forEach(fixture => {
fixtures.forEach((fixture) => {
if (fixture.request.headers.authorization) {
fixture.request.headers.authorization = 'token secret'
fixture.request.headers.authorization = "token secret";
}
})
console.log(JSON.stringify(fixtures, null, 2))
})
});
console.log(JSON.stringify(fixtures, null, 2));
});
```

[coc]: ./CODE_OF_CONDUCT.md
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Features

- Retrieves the repository’s default branch unless `base` branch is set
- Makes multiple file changes using a single commit
- Creates a fork if the authenticated user does not have write access to the repository
- Creates a fork if the authenticated user does not have write access to the
repository
- See [Todos](#todos) for more cool feature ideas! Pull requests welcome!

## Usage
Expand Down Expand Up @@ -41,24 +42,27 @@ octokit
changes: {
files: {
"path/to/file1.txt": "Content for file1",
"path/to/file2.txt": null, // deletes file if it exists
"path/to/file2.png": {
content: "_base64_encoded_content_",
encoding: "base64",
},
"path/to/file3.txt": null, // deletes file if it exists
},
commit: "creating file1.txt & file2.txt",
commit: "creating file1.txt, file2.png and deleting file3.txt",
},
})
.then((pr) => console.log(pr.data.number));
```

You can create a personal access token with the `repo` scope at https://github.com/settings/tokens/new?scopes=repo
You can create a personal access token with the `repo` scope at
https://github.com/settings/tokens/new?scopes=repo

## Todos

- **Editing files** based on current content
Addsupporttopassafunctionasfilecontent,thefunctionwillbecalledwiththecurrentfilecontent,ifpresent.
- **Multiple commits**
Splitupchangesamongmultipleedits
- **Binary files**
Allowtocreate/editfileswithbinarycontent

## LICENSE

Expand Down
45 changes: 33 additions & 12 deletions lib/create-pull-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,23 @@ async function createPullRequest(
});
let latestCommitSha = response.data[0].sha;
const treeSha = response.data[0].commit.tree.sha;

const tree = (
await Promise.all(
Object.keys(changes.files).map(async (path) => {
if (changes.files[path] === null) {
// Deleting a non-existent file from a tree leads to an "GitRPC::BadObjectState" error
const value = changes.files[path];

if (value === null) {
// Deleting a non-existent file from a tree leads to an "GitRPC::BadObjectState" error,
// so we only attempt to delete the file if it exists.
try {
// https://developer.github.com/v3/repos/contents/#get-contents
const response = await octokit.request(
"HEAD /repos/:owner/:repo/contents/:path",
{
owner: fork,
repo,
ref: latestCommitSha,
path,
}
);
await octokit.request("HEAD /repos/:owner/:repo/contents/:path", {
owner: fork,
repo,
ref: latestCommitSha,
path,
});

return {
path,
Expand All @@ -80,10 +81,30 @@ async function createPullRequest(
}
}

// Text files can be changed through the .content key
if (typeof value === "string") {
return {
path,
mode: "100644",
content: value,
};
}

// Binary files need to be created first using the git blob API,
// then changed by referencing in the .sha key
const response = await octokit.request(
"POST /repos/:owner/:repo/git/blobs",
{
owner,
repo,
...value,
}
);
const blobSha = response.data.sha;
return {
path,
mode: "100644",
content: changes.files[path],
sha: blobSha,
};
})
)
Expand Down
58 changes: 58 additions & 0 deletions test/create-binary-file-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { test } = require("tap");
const { RequestError } = require("@octokit/request-error");

const { Octokit: Core } = require("@octokit/core");
const createPullRequest = require("..");
const Octokit = Core.plugin(createPullRequest);

test("happy path", async (t) => {
const fixtures = require("./fixtures/create-binary-file");
const fixturePr = fixtures[fixtures.length - 1].response;
const octokit = new Octokit();

octokit.hook.wrap("request", (_, options) => {
const currentFixtures = fixtures.shift();
const {
baseUrl,
method,
url,
request,
headers,
mediaType,
...params
} = options;

t.equal(currentFixtures.request.method, options.method);
t.equal(currentFixtures.request.url, options.url);

Object.keys(params).forEach((paramName) => {
t.deepEqual(currentFixtures.request[paramName], params[paramName]);
});

if (currentFixtures.response.status >= 400) {
throw new RequestError("Error", currentFixtures.response.status);
}
return currentFixtures.response;
});

const pr = await octokit.createPullRequest({
owner: "gr2m",
repo: "pull-request-test",
title: "A black gif",
body: "because",
head: "patch",
changes: {
files: {
"path/to/1x1-black.gif": {
// https://css-tricks.com/snippets/html/base64-encode-of-1x1px-transparent-gif/
content: "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=",
encoding: "base64",
},
},
commit: "why",
},
});

t.deepEqual(pr, fixturePr);
t.equal(fixtures.length, 0);
});
Loading

0 comments on commit e71fac1

Please sign in to comment.