오픈 소스에 기여해보기
오픈 소스에 기여해보기
Octokit을 사용하여 커밋하기
블로그 서비스 를 제작하던 중, CMS 기능을 추가하기 위하여 외부에서 깃허브에 파일을 가져오고 커밋하는 작업이 필요했습니다.
깃허브에서는 Octokit 이라는 라이브러리를 통해 REST API로 깃허브에 접근할 수 있도록 지원하고 있습니다.
파일을 가져오는 것은 수월하였지만, 파일을 커밋하는 과정에서 큰 문제가 발생하였습니다.
현재 구현한 CMS 기능에서는, 유저가 에디터를 통해 마크다운 문서를 작성할 수 있도록 되어있습니다.

에디터에서 이미지를 업로드 할 경우 클라이언트 측에서 File 객체 형태로 이미지를 저장하고 있다가, 업로드를 누르면 Octokit을 이용하여 업로드하게 되어있습니다.
하지만, Octokit에서 레포지토리에 커밋하기 위한 함수인 octokit.rest.repos.createOrUpdateFile 은 하나의 커밋에 하나의 파일만 담을 수 있습니다.
커밋이 이미지의 개수만큼 발생하는 것을 원하지 않았기 때문에 방법을 찾던 중, 문서에서 트리를 이용하여 커밋할 수 있다는 것을 발견하였습니다.
- 브랜치의 트리 끝에 대한 참조를 가져옵니다.
- 커밋하려는 각 파일에 대해 블롭을 만들고, 그 후에 sha 식별자, 경로, 모드에 대한 참조를 배열에 저장합니다.
- 모든 블롭을 포함하는 새로운 트리를 생성하고, 기존 트리에 대한 참조에 추가하며, 이 새로운 트리에 대한 새로운 sha 포인터를 저장합니다.
- 이 새로운 트리를 가리키는 커밋을 생성하고, 브랜치에 푸시합니다.
과정이 쉬워보이지는 않습니다.
플러그인을 통한 해결 방법
다행히도 octokit-commit-multiple-files 플러그인을 통해 Octokit에 createOrUpdateFiles 함수를 추가하여 하나의 커밋에 여러 파일을 저장할 수 있습니다.
하지만, 이 라이브러리를 사용하는데 가끔씩 이미지 업로드가 정상적으로 되지 않는 것을 확인하였습니다.
해당 오류는 5MB 이상의 이미지를 업로드할 때만 발생하였고, 다음과 같은 에러 로그를 발견하였습니다.
Error creating commit: RangeError: Maximum call stack size exceeded
at RegExp.test (<anonymous>)
at isBase64 (webpack-internal:///(action-browser)/./node_modules/is-base64/is-base64.js:24:52)
at createBlob (webpack-internal:///(action-browser)/./node_modules/octokit-commit-multiple-files/create-or-update-files.js:181:14)
at eval (webpack-internal:///(action-browser)/./node_modules/octokit-commit-multiple-files/create-or-update-files.js:95:47)
at Array.map (<anonymous>)
at eval (webpack-internal:///(action-browser)/./node_modules/octokit-commit-multiple-files/create-or-update-files.js:87:45)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
Error creating commit: Maximum call stack size exceededoctokit-commit-multiple-files 라이브러리는 isBase64 라는 외부 라이브러리를 통해 base64를 체크하고 있습니다.
하지만 isBase64는 마지막 커밋이 4년전으로 더이상 유지보수가 되지 않는 라이브러리로, 해당 이슈를 가진 사람들이 많았습니다.
따라서 해당 라이브러리를 사용하는 대신, 구현한 함수를 넣어 해당 오류가 해결되는지 확인하기 위하여 클론 후 다음과 같은 함수로 대체하였습니다.
function isBase64(str) {
var notBase64 = /[^A-Z0-9+\/=]/i
const isString = typeof str === 'string' || str instanceof String
if (!isString) {
let invalidType
if (str === null) {
invalidType = 'null'
} else {
invalidType = typeof str
if (
invalidType === 'object' &&
str.constructor &&
str.constructor.hasOwnProperty('name')
) {
invalidType = str.constructor.name
} else {
invalidType = `a ${invalidType}`
}
}
throw new TypeError(`Expected string but received ${invalidType}.`)
}
const len = str.length
if (!len || len % 4 !== 0 || notBase64.test(str)) {
return false
}
const firstPaddingChar = str.indexOf('=')
return (
firstPaddingChar === -1 ||
firstPaddingChar === len - 1 ||
(firstPaddingChar === len - 2 && str[len - 1] === '=')
)
}이후 정상적으로 업로드가 되는 것을 확인하였고, 이를 라이브러리 제작자에게 알리기 위해 PR을 작성하였습니다.

2주 정도 후 다음과 같은 코멘트와 함께 머지된 것을 확인할 수 있었습니다.

결론
생각지도 못하게 오픈 소스에 기여하는 경험을 해볼 수 있었습니다.
엄청나게 주요한 문제를 발견했다거나, 문제를 해결하는 코드를 직접 작성한 것은 아니지만 문제를 발견하고 해결하는 방법을 제시하는 과정에서 여러가지를 배울 수 있었습니다.

또한, 컨트리뷰터에 포함되니 나름 뿌듯한 것 같습니다.