GitHub Actions을 이용해 프로젝트를 지속적으로 관리해보기
📄

GitHub Actions을 이용해 프로젝트를 지속적으로 관리해보기

Created
Jun 27, 2021 05:50 AM
Tags
ci
github actions
notion image
 
GitHub Actions, 자주 들어봤을 것이다. (아니라면 여기 참고)
Push, Scheduled(cron) 등 특정 Event가 Triggered 되면, 정의한 Actions가 실행되는 것인데...
이를 이용해 다양한 작업을 할 수 있다.
 
여기서는 GitHub Actions를 이용해 GitHub Issues를 생성했던 시행착오를 말해보고자 한다.

어쩌다가?...

먼저 왜 이 글을 쓰게 되었는지 말해보자면,
현재 나는 ViteJS 문서를 번역하는 리포지터리를 관리하고 있다.
 
처음에 번역을 시작했을 때에는 무지하게도 큰 변경 사항이 없을 것이라 판단하였고,
결국 원본 문서와의 Sync는 생각하지 않고 바로 번역 작업을 시작해버렸다.
 
당연하게도 이후 원본과의 Sync가 맞지 않는다는 피드백이 들어왔고,
아래와 같이 프로젝트를 재구성하자 생각하게 되었다.
 
as-is
  • main : 번역을 진행하는 브랜치
  • gh-pages : 배포용 브랜치
 
to-be
  • main : 번역을 진행하는 브랜치
  • gh-pages : 배포용 브랜치
  • sync : 원본과의 Sync를 위한 브랜치
    • 매일 자정(UTC), 원본 리포지터리와의 Sync 진행
    • Sync 시, Vite 문서 디렉터리인 docs 디렉터리만 diff
    • 차이점이 있다면 해당 커밋만을 Issues로 남김 & merge
 
솔직하게 말해서, 위 방안이 최선인지는 잘 모르겠다.
번역, 그리고 지속적으로 Sync가 필요한 프로젝트는 처음 해봐서... 미숙한 점이 많다.
그래도 해당 내용은 다른 한 분에게 피드백을 받고 진행한 것이라 불안은 좀 덜 하다.
 
참고로, 바로 sync 에서 main 으로 merge 하지 않는 이유는... 간단하다.
main 은 말했듯이 번역 작업이 진행되는 브랜치이기에,
무엇을 기준으로 진행할 지 명확하지 않아 바로 merge 해버리면 Conflict가 되기 때문...
 
아무튼 사담은 이쯤 하고, 이를 어떻게 진행했는지나 보도록 하자.

sync.yml

Actions 구성을 위해 .github/workflows/sync.yml 이라는 파일을 하나 만들어줬다.
파일의 내용은 아래와 같다.
 
name: Sync

on:
  push:
    branches:
      - main
  schedule:
    - cron: "0 0 * * *"

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Pull changes
        run: |
          git remote add upstream https://github.com/vitejs/vite
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"

      - name: Fetch and Checkout datas
        run: |
          git fetch upstream
          git fetch origin
          git checkout --track origin/sync

      - name: Diff
        run: |
          git diff sync upstream/main -- docs >> sync
          git log sync..upstream/main -- docs >> sync

      - name: Merge from upstream
        run: |
          git pull upstream main
          git push origin sync
          git checkout main

      - name: Install dependencies
        uses: Borales/actions-yarn@v2.3.0
        with:
          cmd: install

      - name: Run sync script
        uses: Borales/actions-yarn@v2.3.0
        with:
          cmd: sync ${{ github.token }}
 
글의 주제와는 큰 관련이 없지만, 그래도 하나씩 떼어내 살펴보자면...
 
name: Sync

on:
  push:
    branches:
      - main
  schedule:
    - cron: "0 0 * * *"
Triggered 될 Events를 명시
 
  • name : Actions의 이름
  • on.push.branches , on.push.schedule : 등록된 Triggers
    • main : main 브랜치에 Pushed 되면 Actions 실행
    • cron : UTC 기준 매일 자정에 Actions 실행 (참고)
 
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      # ...
실행할 스크립트를 명시 (스크립트는 순차적으로 실행됨)
 
 
      - name: Pull changes
        run: |
          git remote add upstream https://github.com/vitejs/vite
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"

      - name: Fetch and Checkout datas
        run: |
          git fetch upstream
          git fetch origin
          git checkout --track origin/sync

      - name: Diff
        run: |
          git diff sync upstream/main -- docs >> sync
          git log sync..upstream/main -- docs >> sync

      - name: Merge from upstream
        run: |
          git pull upstream main
          git push origin sync
          git checkout main
Git 관련 작업 진행
 
  • Pull changes : 원본 리포지터리(upstream)를 remote add 및 Git user settings
  • Fetch and Checkout datas : 각 Remote repo에서 최신 내용을 가져온다
    • 이후 origin/sync 베이스로 checkout ⇒ Head는 local/sync 브랜치로 가게 된다
  • Diff : upstream/maindocs 디렉터리만 diff 및 변경사항 커밋 추적(log)
    • 이 결과를 sync 라는 파일로 Append
  • Merge from upstream : local/sync 브랜치에 upstream/mainpull
    • 이후 origin/syncpush
    • Package scripts 실행을 위해 main 브랜치로 checkout
 
      - name: Install dependencies
        uses: Borales/actions-yarn@v2.3.0
        with:
          cmd: install

      - name: Run sync script
        uses: Borales/actions-yarn@v2.3.0
        with:
          cmd: sync ${{ github.token }}
Package Scripts 실행
 
  • Install dependencies : Package scripts 실행을 위해 Dependencies를 설치
  • Run sync script : 앞서 생성한 sync 파일을 기반으로 조물조물
    • 이 때 GITHUB_TOKEN 을 같이 넘기며
    • Diff 및 Commit hash/msg 파싱해 Token을 이용해 Issues를 남긴다
 
대충 이러한 흐름으로 Sync 작업이 진행된다.

sync Package script

일단, package.jsonscripts 에는 아래와 같이 명시되어 있다.
 
"scripts": {
  "dev": "vitepress dev .",
  "build": "vitepress build .",
  "serve": "vitepress serve .",
  "rewrite-title": "node scripts/rewrite-title",
  "sync": "node scripts/sync"
}
package.jsonscripts
 
여기서 봐야 할 것은 sync 부분, 아래의 JavaScript 코드를 실행한다.
 
/* scripts/sync/index.js */

const fs = require('fs');
const splitCommit = require('./splitCommit');
const { Octokit } = require('@octokit/rest');

const syncFilePath = require('path').resolve(__dirname, '..', '..', 'sync');
const token = process.argv[2];

fs.readFile(syncFilePath, (err, file) => {
  if (err) {
    return console.error(err);
  }

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

  splitCommit(file.toString()).forEach(({ diff, hash, msg }) =>
    octokit.rest.issues.create({
      owner: 'vitejs-kr',
      repo: 'docs-next',
      title: `[SYNC] ${msg}`,
      labels: ['sync'],
      body: [
        `${msg} ([${hash.slice(0, 7)}](https://github.com/vitejs/vite/commit/${hash}))`,
        '',
        '---',
        '',
        '````diff',
        diff,
        '````',
      ].join('\n'),
    }));
});
splitCommit.js여기 참고 (테스트 코드를 보면 좀 더 이해가 쉬울 것이다)
 
마찬가지로 이 코드도 하나씩 보자
 
const fs = require('fs');
const splitCommit = require('./splitCommit');
const { Octokit } = require('@octokit/rest');
Import 되는 모듈 목록
 
  • fs : NodeJS의 File System 모듈
  • splitCommit : Diff 및 Commit hash/msg를 파싱하는 용도
 
const syncFilePath = require('path').resolve(__dirname, '..', '..', 'sync');
const token = process.argv[2];
작업 진행을 위한 설정 값 목록
 
  • syncFilePath : 앞서 위에서 만든 sync 파일에 대한 경로
  • token : 전달되는 GitHub Token
    • 이를 이용해 GitHub API를 사용한다
 
	const octokit = new Octokit({ auth: token });

	splitCommit(file.toString()).forEach(({ diff, hash, msg }) =>
    octokit.rest.issues.create({
      owner: 'vitejs-kr',
      repo: 'docs-next',
      title: `[SYNC] ${msg}`,
      labels: ['sync'],
      body: [
        `${msg} ([${hash.slice(0, 7)}](https://github.com/vitejs/vite/commit/${hash}))`,
        '',
        '---',
        '',
        '````diff',
        diff,
        '````',
      ].join('\n'),
    }));
Git difflog 메시지를 GitHub API를 이용해 Issues로 남기는 코드
 
  • octokit : GitHub API 사용을 위해 Auth 초기화
  • splitCommit : 저장된 메시지를 파싱해 아래 세 개가 정보가 담긴 객체의 배열로 반환
    • diff : git diff 결과
    • hash : Commit 해시
    • msg : Commit 메시지
  • octokit.rest.issues.create : 이슈 생성, 아래의 데이터를 파라미터로 받는다
    • owner : GitHub 계정명
    • repo : GitHub 리포지터리 이름
    • title : Issue title
    • labels : Issue labels
    • body : Issue body
 
편의를 위해 Issues body에 원본(upstream) 커밋으로 바로 연결할 수 있도록 구성해놓았다.
또한, git diff 확인을 좀 더 쉽게 하기 위해 Diff code highlight를 적용해 구현했다.
솔직하게 말해 후자의 것은 git diff 를 어떻게 표현해야 하나 고민하다가... 처음 알았다.
 
아무튼 이러한 방식으로 코드가 구성된다.
참고로 위와 같이 authGITHUB_TOKEN 을 전달하게 되면, GitHub bot이 Issue를 남기게 된다.
PAT 같은 토큰을 전달해버리면... 해당 계정으로 Issue가 생성되게 되어버리고...
그럼 해당 계정을 관리하기 조금 복잡해지게 된다. 약간 일일커밋 어뷰징 같기도 하고...

Run actions

이를 실행하기 전, 기존 리포지터리를 아래와 같이 재구성해줘야 정상적으로 작동한다.
 
기존 리포지터리 환경
  • origin/main 에서 번역 작업 진행
  • upstream/main 과는 Commit history가 일치하지 않음
  • origin/main 에서는 upstream/maindocs 디렉터리가 루트 디렉터리가 됨 ⇒ upstream 에서 docs 디렉터리만 따로 떼와 작업하던 중이었음
 
하고자 하는 것
  • origin/sync 는 매일 자정에 upstream/main 과 동기화 진행
  • 동기화 후 docs 디렉터리에서 변경 사항이 발생되면 이를 Issues로 알림
  • 기존 Commit history 유실되지 않고 작업 진행
 
리포지터리 재구성
  1. upstream 리포 Fork 및 Local로 Clone
  1. Local에서 Git remote를 아래와 같이 수정 ⇒ origin : vitejs-kr/docs-nextupstream : vitejs/vite
  1. Clone하면 local/main 브랜치가 되는데, 이를 local/sync 브랜치로 이름 변경 ⇒ 현재 브랜치는 local/sync
  1. origin/sync 로 Push
  1. checkout -b main 으로 main 브랜치 생성 및 docs 디렉터리를 루트 디렉터리로 만듦 ⇒ docs 디렉터리 내 모든 파일을 루트 디렉터리로 옮기고, 이후 기존 파일 삭제 및 커밋 ⇒ 현재 브랜치는 local/main
  1. origin 리포를 Local로 fetch
  1. --allow-unrelated-histories 옵션을 이용해 origin/mainmergegit merge --allow-unrelated-histories origin/main
  1. 커밋 및 origin/main 으로 Push
 
재구성 이후...
  • Push 직후, sync.yml 에서 정의한 Actions 실행
  • origin/sync 기준 docs 디렉터리에서 변경사항 발생되면 이를 Issues로 알림
  • 매일 자정(UTC), sync.yml 에서 정의한 Actions 실행
 
기존에는 어떤 변경사항이 발생되었는지 확인하지 못해 조금씩 차이가 있었는데,
이를 이제 자동으로 관리할 수 있게 된 것.
 
아무튼, 이 곳의 내용을 바탕으로 조금씩만 응용하면
자신이 원하는 방식으로 프로젝트를 지속적으로 관리할 수 있을 것이다.
 

Loading Comments...