본문 바로가기
개발/Git, GitHub

깃허브 - RESTful API로 파일 쓰기 (Update GitHub Files with PUT)

by 피로물든딸기 2023. 6. 23.
반응형

깃허브 데스크탑으로 프로젝트 관리하기 강의 오픈!! (인프런 바로가기)

 

Git / GitHub 전체 링크

Node JS 전체 링크

 

참고

- RESTful API로 파일의 SHA 구하기

- RESTful API로 파일 읽기

- RESTful API로 파일 쓰기

- RESTful API로 파일 생성, 삭제하기

- 깃허브 RESTful API로 파일 편집기 만들기

- https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28 

 

RESTful API로 파일을 읽었다면, 이번에 파일의 내용을 변경해보자.

https://github.com/bloodstrawberry/auto-test


SHA 알아오기

 

파일을 업데이트하기 위해서는 sha가 필요하다.

RESTful API로 깃허브에 저장된 파일의 sha를 알아낼 수 있다.

 

링크의 PUT 예시를 참고하여 아래와 같이 코드를 수정한다.

myKey에는 직접 발급 받은 토큰을 추가하면 된다.

let myKey = "...";

const { Octokit } = require("@octokit/rest");

const octokit = new Octokit({
  auth: myKey,
});

async function test() {
  const result = await octokit.request(
    "PUT /repos/bloodstrawberry/auto-test/contents/test/apitest.txt",
    {
      owner: "bloodstrawberry",
      repo: "auto-test",
      path: "test/apitest.txt",
      message: "commit message!",
      
      sha: "ebcc5aa5ce2f4514b046c8841f5cc48b3cfd1a10",
      
      committer: {
        name: "bloodstrawberry",
        email: "bloodstrawberry@github.com",
      },
      content: `${btoa("change file!!\nYou can do it!!\n")}`,
    }
  );

  console.log(result);
}

test();

 

참고로 내용은 base64로 인코딩을 해야하기 때문에 btoa 메서드를 사용해야 한다.

content: `${btoa("change file!!\nYou can do it!!\n")}`,

 

정상 응답은 다음과 같다.

PS D:\github\node-server\macro> node .\getApi.js
{
  status: 200,
  url: 'https://api.github.com/repos/bloodstrawberry/auto-test/contents/test/apitest.txt',
  headers: {
    'access-control-allow-origin': '*',
    'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset',
    'cache-control': 'private, max-age=60, s-maxage=60',
    'content-encoding': 'gzip',
    'content-security-policy': "default-src 'none'",
    'content-type': 'application/json; charset=utf-8',
    date: 'Fri, 23 Jun 2023 11:59:57 GMT',
    etag: 'W/"3fb0250650e60e868ebfd58b65fa6efc779333d15685a651c2097e0aaf3b4f18"',
    'github-authentication-token-expiration': '2023-07-23 11:03:26 UTC',
    'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin',
    server: 'GitHub.com',
    'strict-transport-security': 'max-age=31536000; includeSubdomains; preload',
    'transfer-encoding': 'chunked',
    vary: 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With',
    'x-accepted-oauth-scopes': '',
    'x-content-type-options': 'nosniff',
    'x-frame-options': 'deny',
    'x-github-api-version-selected': '2022-11-28',
    'x-github-media-type': 'github.v3; format=json',
    'x-github-request-id': '0D22:4669:3B952C:453D21:6495893D',
    'x-oauth-scopes': 'repo',
    'x-ratelimit-limit': '5000',
    'x-ratelimit-remaining': '4985',
    'x-ratelimit-reset': '1687523661',
    'x-ratelimit-resource': 'core',
    'x-ratelimit-used': '15',
    'x-xss-protection': '0'
  },
  data: {
    content: {
      name: 'apitest.txt',
      path: 'test/apitest.txt',
      sha: '9126c33710f0d9488dd1b51ade246311c54c7daf',
      size: 30,
      url: 'https://api.github.com/repos/bloodstrawberry/auto-test/contents/test/apitest.txt?ref=main',
      html_url: 'https://github.com/bloodstrawberry/auto-test/blob/main/test/apitest.txt',
      git_url: 'https://api.github.com/repos/bloodstrawberry/auto-test/git/blobs/9126c33710f0d9488dd1b51ade246311c54c7daf',
      download_url: 'https://raw.githubusercontent.com/bloodstrawberry/auto-test/main/test/apitest.txt',
      type: 'file',
      _links: [Object]
    },
    commit: {
      sha: '3bc3c9c12561d2dbc831ded16a580c789058c16e',
      node_id: 'C_kwDOJM4Ni9oAKDNiYzNjOWMxMjU2MWQyZGJjODMxZGVkMTZhNTgwYzc4OTA1OGMxNmU',
      url: 'https://api.github.com/repos/bloodstrawberry/auto-test/git/commits/3bc3c9c12561d2dbc831ded16a580c789058c16e',
      html_url: 'https://github.com/bloodstrawberry/auto-test/commit/3bc3c9c12561d2dbc831ded16a580c789058c16e',
      author: [Object],
      committer: [Object],
      tree: [Object],
      message: 'commit message!',
      parents: [Array],
      verification: [Object]
    }
  }
}

 

 깃허브에 돌아가서 변경된 파일을 확인해보자.


다시 업데이트하기

 

SHA는 깃허브에서 파일이나 디렉터리가 변경되면 추적하기 위한 식별자이다.

따라서 파일을 수정하면 sha가 바뀌는 것을 알 수 있다. (위의 경우 ebcc5...1a10)

 

따라서 위에서 만든 코드를 실행하면 409 에러가 발생한다.

409 에러는 서버에 있는 파일보다 오래된 파일을 업로드하면서 충돌이 발생하면 받는 에러다.

RequestError [HttpError]: test/apitest.txt does not match 0d5a690c8fad5e605a6e8766295d9d459d65de42
    at D:\github\node-server\node_modules\@octokit\request\dist-node\index.js:122:21
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async test (D:\github\node-server\macro\getApi.js:10:18) {
  status: 409,

 

따라서 매번 현재의 sha를 구하는 메서드를 추가하고, 코드를 실행하면 된다.

let myKey = "...";

const { Octokit } = require("@octokit/rest");

const octokit = new Octokit({
  auth: myKey,
});

async function getSHA() {
  const result = await octokit.request('GET /repos/bloodstrawberry/auto-test/contents/test/apitest.txt', {
    owner: 'bloodstrawberry',
    repo: 'auto-test',
    path: 'test/apitest.txt',
  })

  return result.data.sha;
}

async function test() {
  const currentSHA = await getSHA();
  const result = await octokit.request(
    "PUT /repos/bloodstrawberry/auto-test/contents/test/apitest.txt",
    {
      owner: "bloodstrawberry",
      repo: "auto-test",
      path: "test/apitest.txt",
      message: "commit message!",
      sha: currentSHA,
      committer: {
        name: "bloodstrawberry",
        email: "bloodstrawberry@github.com",
      },
      content: `${btoa("change file!!\nYou can do it!!\n 222222222222\n")}`,
      headers: {
        'X-GitHub-Api-Version': '2022-11-28'
      }
    }
  );

  console.log(result);
}

test();

 

이제 매번 업데이트가 계속 잘 되는 것을 알 수 있다.

 

만약 파일을 최초로 만들고 싶다면 sha 정보를 제외하면 된다.

async function createNewFile() {
  const result = await octokit.request(
    "PUT /repos/bloodstrawberry/auto-test/contents/test/apitest.txt",
    {
      owner: "bloodstrawberry",
      repo: "auto-test",
      path: "test/apitest.txt", // new file이어야 성공
      message: "commit message!",
      committer: {
        name: "bloodstrawberry",
        email: "bloodstrawberry@github.com",
      },
      content: `${btoa("change file!!\nYou can do it!!\n 222222222222\n")}`,
      headers: {
        'X-GitHub-Api-Version': '2022-11-28'
      }
    }
  );

  console.log(result);
}
반응형

댓글