kimyu0218
  • [프로젝트 관리] GitHub Actions로 PR에 라벨, 이슈, 담당자 등록하기
    2024년 02월 18일 20시 44분 08초에 업로드 된 글입니다.
    작성자: @kimyu0218

    PR의 설정들을 활용하면 개발 및 협업 프로세스를 향상시킬 수 있다. 라벨, 이슈, 담당자를 등록함으로써 변경사항을 쉽게 추적할 수 있고, 누가 어떤 작업을 담당하고 있는지 확인할 수 있다.

    `feature`, `bug` 같은 라벨을 통해 PR의 목적을 빠르게 이해할 수 있으며 우선순위를 판단하는 데에도 도움을 준다.

    하지만 PR을 설정하는 과정은 굉장히 번거로운 작업이다. 따라서 자동화된 프로세스를 통해 각 PR에 알맞은 설정 정보를 등록함으로써 개발자가 실제 개발 작업에 집중할 수 있도록 만들 것이다.

    🚨 PR을 수동으로 설정하면 실수가 발생할 수 있으므로 자동화된 프로세스를 통해 작업의 정확성을 보장한다.

    아래 코드는 액션 파일의 전문이다. PR이 오픈되었을 때 자동으로 설정들을 등록한다. 아래에서 자세히 살펴보도록 하자.

    name: Configure PR When PR Opened
    
    on:
      pull_request:
        types: [opened]
    
    permissions:
      contents: read
    
    jobs:
      check-title:
        name: Check PR Title
        runs-on: ubuntu-latest
    
        steps:
          - name: Check PR title
            uses: deepakputhraya/action-pr-title@master
            with:
              regex: '((Be|Fe|Devops|Be,fe|Fe,be)\/(feature|bugfix|refactor)(,(feature|bugfix|refactor))*\/#[\d]+( #[\d]+)*.+|release v[\d]+\.[\d]+\.[\d]+)'
              github_token: ${{ secrets.ADD_TO_PROJECT_PAT }}
    
      set-label:
        name: Set Labels to PR
        needs: check-title
        runs-on: ubuntu-latest
        permissions:
          contents: read
          pull-requests: write
    
        steps:
          - uses: TimonVS/pr-labeler-action@v5
            with:
              github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
              configuration-path: .github/pr-labels.yml
    
      set-issue:
        name: Set Issue to PR
        needs: check-title
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/github-script@v7
            if: github.event.pull_request.base.ref != 'release'
            with:
              github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
              script: |
                const prTitle = context.payload.pull_request.title;
                const issueNumbers = prTitle.match(/#(\d+)/g);
                const prefix = issueNumbers ? issueNumbers.reduce((acc, curr) => `${acc}🔮 resolved ${curr}\n`, "") : '';
                const body = context.payload.pull_request.body.replace(/(🔮 resolved #\d+)+/g, '');
    
                github.rest.pulls.update({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  pull_number: context.payload.number,
                  body: `${prefix}${body}`,
                });
    
                issueNumbers?.forEach((num) => {
                  github.rest.issues.update({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: num.replace('#', ''),
                    state: 'open',
                  });
                });
    
      set-assignee:
        name: Set Assignee to PR
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/github-script@v7
            if: github.event.pull_request.base.ref != 'release'
            with:
              github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
              script: |
                const sender = context.payload.sender.login;
                const assignees = context.payload.pull_request.assignees;
                const newAssignees = assignees ?? [];
                newAssignees.push(sender);
    
                github.rest.issues.addAssignees({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.payload.number,
                  assignees: newAssignees,
                });

     

    PR 제목 검사하기

    PR 제목은 해당 변경사항에 대한 간략한 설명을 제공해야 한다. 다른 개발자들이 어떤 작업을 수행했는지 이해하는 데 도움을 주기 때문이다.

    🌳 브랜치명 `(1)/(2)/(3)`
    • 분야 : `FE`, `BE`, `DEVOPS`
    • 분류 : `feature`, `bugfix`, `refactor`
    • 이슈 : `#(이슈 번호)-(이슈 이름)`

    현재 프로젝트에서는 브랜치명을 바탕으로 PR 제목을 생성한다. 아래는 위 네이밍 규칙을 바탕으로 나올 수 있는 브랜치명의 예시들이다.

    • `FE/feature/#1-기능-이름`
    • `BE/bugfix/#2-어떤-API`
    • `DEVOPS/feature/#4-라벨-자동-설정`
    • `FE,BE/feature/#5-FE랑-BE-같이-개발`
    • `DEVOPS/bugfix,refactor/#6-버그픽스-리팩토링-같이-함`

    PR 제목도 브랜치 네이밍 규칙과 거의 유사하다. 하지만 하이픈이 공백으로 대체되고, 첫문자 외의 모든 알파벳은 소문자로 치환된다. (`DEVOPS/bugfix,refactor/#6-버그픽스-리팩토링-같이-함` → `Devops/bugfix,refactor/#6 버그픽스 리팩토링 같이 함`)
     
    PR 제목에 패턴이 있으므로 PR이 유효한 제목을 갖고 있는지 검사하는 작업을 작성해줄 것이다. action-pr-title이라는 외부 액션을 이용해보자.

    check-title:
      name: Check PR Title
      runs-on: ubuntu-latest
    
      steps:
        - name: Check PR title
          uses: deepakputhraya/action-pr-title@master
          with:
            regex: '((Be|Fe|Devops|Be,fe|Fe,be)\/(feature|bugfix|refactor)(,(feature|bugfix|refactor))*\/#[\d]+( #[\d]+)*.+|release v[\d]+\.[\d]+\.[\d]+)'
            github_token: ${{ secrets.ADD_TO_PROJECT_PAT }}

    `regex`를 이용해서 앞의 규칙을 적용해주기만 하면 된다! (`release~`는 릴리즈를 위한 PR 제목으로, 우리 프로젝트에서 브랜치명을 사용하지 않는 유일한 예외다.
     

    PR 라벨 붙이기

    PR이 정상적인 제목을 갖고 있다면 적절한 라벨을 붙여줘야 한다. pr-labeler는 브랜치명을 바탕으로 라벨을 붙여주는 액션이다. 라벨을 커스텀하기 위해서는 먼저 설정 파일을 작성해줘야 한다.

    feature: "*feature*"
    bug: "*bugfix*"
    refactor: "*refactor*"
    release: "release*"
    BE: ["Be/*", "Fe,be/*", "Be,fe/*"]
    FE: ["Fe/*", "Fe,be/*", "Be,fe/*"]
    DevOps: "Devops/*"

    위 파일은 우리 프로젝트에서 활용하는 라벨들이다. `feature: "*feature*"`는 제목에 `feature`라는 문자열이 포함된 경우 `feature` 라벨을 붙인다는 의미다.

    set-label:
      name: Set Labels to PR
      needs: check-title
      runs-on: ubuntu-latest
      permissions:
        contents: read
        pull-requests: write
    
      steps:
        - uses: TimonVS/pr-labeler-action@v5
          with:
            github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
            configuration-path: .github/pr-labels.yml

    이제 리드미를 참고하여 작업을 작성해보자. `configuration-path`에 앞서 작성한 라벨 설정 파일의 위치를 지정하면 된다.

     

    PR에 이슈 등록하기

    깃헙에서는 PR 본문에 다음 키워드를 사용하여 PR과 이슈를 연결할 수 있다.

    • `closed #이슈번호` - PR이 병합되면 연관된 이슈가 같이 닫힌다.
    • `fixed #이슈번호` - PR이 닫힌 후에도 연관된 이슈가 열려있다.
    • `resolved #이슈번호` - PR이 병합되면 연관된 이슈가 닫히지만 다시 열릴 수 있다.

    추후에 버그로 인해 다시 PR이 열릴 수도 있기 때문에 나는 `resolved` 키워드를 이용하여 이슈를 연결해줄 것이다. 이슈 번호는 PR 제목에서 확인할 수 있다. `Devops/feature/#100-이슈-제목`

    set-issue:
      name: Set Issue to PR
      needs: check-title
      runs-on: ubuntu-latest
    
      steps:
        - uses: actions/github-script@v7
          if: github.event.pull_request.base.ref != 'release'
          with:
            github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
            script: |
              const prTitle = context.payload.pull_request.title;
              const issueNumbers = prTitle.match(/#(\d+)/g);
              const prefix = issueNumbers ? issueNumbers.reduce((acc, curr) => `${acc}🔮 resolved ${curr}\n`, "") : '';
              const body = context.payload.pull_request.body.replace(/(🔮 resolved #\d+)+/g, '');
    
              github.rest.pulls.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.payload.number,
                body: `${prefix}${body}`,
              });
    
              issueNumbers?.forEach((num) => {
                github.rest.issues.update({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: num.replace('#', ''),
                  state: 'open',
                });
              });

    github-script는 깃헙 API를 간편하게 이용하기 위한 액션이다. 가장 먼저 깃헙 API를 통해 PR 제목을 조회하여 이슈 번호를 파싱해야 한다.

    const prTitle = context.payload.pull_request.title;
    const issueNumbers = prTitle.match(/#(\d+)/g);

    정규식을 통해 이슈 번호들을 파싱하면 `resolved #이슈번호`를 본문 최상단에 적는다. 그리고 본문 내용을 완성하고 난 후에는 `update`를 통해 깃헙 본문을 업데이트하여 이슈를 연결시킨다.

    const prefix = issueNumbers ? issueNumbers.reduce((acc, curr) => `${acc}🔮 resolved ${curr}\n`, "") : '';
    const body = context.payload.pull_request.body.replace(/(🔮 resolved #\d+)+/g, '');
     
    github.rest.pulls.update({
      owner: context.repo.owner,
      repo: context.repo.repo,
      pull_number: context.payload.number,
      body: `${prefix}${body}`,
    });

    마지막으로 이슈의 상태를 바꿔야 한다. 버그픽스를 목적으로 닫힌 이슈에 대해 PR을 날리는 상황이 발생할 수 있기 때문이다. 깃헙 API를 통해 해당 이슈 번호를 가진 이슈를 다시 열어준다.

    issueNumbers?.forEach((num) => {
      github.rest.issues.update({
        owner: context.repo.owner,
        repo: context.repo.repo,
        issue_number: num.replace('#', ''),
        state: 'open',
      });
    });

     

    PR에 담당자 등록하기

    이번엔 PR에 관여한 담당자를 등록할 것이다. PR을 작성한 사람이 해당 작업을 맡은 담당자이므로 깃헙 API를 통해 PR을 오픈한 사람을 등록하면 된다.

    set-assignee:
      name: Set Assignee to PR
      runs-on: ubuntu-latest
    
      steps:
        - uses: actions/github-script@v7
          if: github.event.pull_request.base.ref != 'release'
          with:
            github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
            script: |
              const sender = context.payload.sender.login;
              const assignees = context.payload.pull_request.assignees;
              const newAssignees = assignees ?? [];
              newAssignees.push(sender);
    
              github.rest.issues.addAssignees({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.number,
                assignees: newAssignees,
              });

    이제 PR을 오픈할 때 라벨, 이슈, 담당자 등록을 누락하더라도 깃헙 액션이 자동으로 해당 설정들을 등록해준다!!


    참고자료

    댓글