포스트

Svn에 커밋이 올라온다면 디스코드 알림이 오게 해보자

이 글은 제 개인적인 공부를 위해 작성한 글입니다.
틀린 내용이 있을 수 있고, 피드백은 환영합니다.


목적


SVN을 사용하다보니 팀 환경에서 커밋이 언제 발생했는지 실시간으로 알 수 없고, 어떤 작업자가 어떤 파일을 수정했는지 확인하기도 어려웠다.

그렇다보니 변경 사항이 공유되지 않아, 작업 중 충돌이 발생하거나 중복 작업이 생기는 문제가 발생하였다.

그래서 평소에 깃허브 커밋 디스코드 알림처럼 SVN도 동일하게 구현하면 좋을 듯해서 시도하였다.


구현


일단 나는 Visual SVN 서버를 AWS EC2 윈도우 인스턴스에 올려두고 사용했다.


  1. 커밋 정보 수집 스크립트 작성

우선 커밋 정보를 수집하는 코드는 go 언어로 작성하였고 아래와 같다.

물론 이번이 go 언어를 처음 사용해보는 터라 대부분의 코드 작성은 AI로 구성하였는데, go 언어를 사용해보니 설치/빌드 과정이 너무 편리해서 간단한 프로그램 만드는거면 자주 사용해야겠다

스크립트는 svnlook 명령어를 사용해서 변경내역/리비전 넘버/작성자 등의 커밋 정보를 수집한다.


혹시 에러가 발생하면 간단하게 로그도 찍게 두었다.

EC2에 로그 없이 exe 파일 넣었는데, 거기서 에러 뜨면 매우 귀찮기 때문에..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"time"
)

const (
	webhookURL = "https://discord.com/api/webhooks/your-discord-webhooks-api"
	logFile    = "log.txt"
)

type DiscordMessage struct {
	Content string `json:"content"`
}

func logError(err error) {
	f, fileErr := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if fileErr != nil {
		fmt.Println("Log file error:", fileErr)
		return
	}
	defer f.Close()
	timestamp := time.Now().Format("2006-01-02 15:04:05")
	logMsg := fmt.Sprintf("[%s] %v\n", timestamp, err)
	f.WriteString(logMsg)
}

func runSvnlook(arg string, repoPath, revision string) (string, error) {
	cmd := exec.Command("svnlook", arg, "-r", revision, repoPath)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out
	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("svnlook %s error: %v - output: %s", arg, err, out.String())
	}
	return out.String(), nil
}

func sendDiscordMessage(repoPath, revision, author, changedFiles string) error {
	message := fmt.Sprintf(
		"SVN 커밋 발생!\nRepository: `%s`\nRevision: `%s`\nAuthor: `%s`\n\n변경된 파일:\n```\n%s```",
		repoPath, revision, author, changedFiles)

	payload := DiscordMessage{Content: message}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return fmt.Errorf("Discord webhook error: %s", resp.Status)
	}

	return nil
}

func main() {
	if len(os.Args) < 3 {
		logError(fmt.Errorf("Usage: discord_notify <REPO_PATH> <REVISION>"))
		return
	}

	repoPath := os.Args[1]
	revision := os.Args[2]

	author, err := runSvnlook("author", repoPath, revision)
	if err != nil {
		logError(err)
		author = "(작성자 정보 없음)"
	} else {
		author = string(bytes.TrimSpace([]byte(author)))
	}

	changedFiles, err := runSvnlook("changed", repoPath, revision)
	if err != nil {
		logError(err)
		changedFiles = "(변경된 파일 목록을 가져오는 데 실패했습니다)"
	}

	if err := sendDiscordMessage(repoPath, revision, author, changedFiles); err != nil {
		logError(err)
	}
}

뭐 go 언어 설치/빌드하는 법은 구글에 검색하면 쉽게 할 수 있고, svnlook 명령어는 svn 서버에서만 실행되기에 클라이언트에서 테스트해도 svnlook: E720002: Can't open file 'format': 라는 에러만 뜨고 안될 것이다. (내가 30분동안 그랬음)

만약 go 빌드가 안된다면? 빌드를 하고자 하는 스크립트가 있는 폴더에서 go mod init file_name을 하셨나요?


빌드된 exe 파일을 ec2 인스턴스 하드 디스크의 적당한 위치에 넣어주자.

아래 post-commit.bat 파일에 your_path\your_file_name.exe 이 그 적당한 위치와 파일 명이 된다.

그리고 RDP 클라이언트를 사용한다면 원격 데스크톱 연결(Remote Desktop Connection)으로 편하게 디스크 연동을 할 수 있더라. 아래에 참고 링크를 확인해보세요.


  1. post-commit.bat 작성

이 파일은 커밋 이후에 svn 서버에서 자동으로 실행되는 배치 파일이라고 한다.

생성 경로는 svn 서버에서 Repositories\project_name\hooks\ 내에 만들면 된다.

메모장으로 작성하고 확장자 변경을 잊지 말자!

1
2
3
@echo off

C:\your_path\your_file_name.exe %1 %2


결과


img

커밋이 올라올때마다 실시간으로 디스코드에 바로 알람이 떠서 너무 좋다..

다른 개발자가 에셋을 수정했을 때 바로바로 최신화 할 수 있고, 어떤 파일이 변경되었는지 디스코드에서 어느정도 확인도 가능하다.

걱정되는 점은, 디스코드에도 메세지 제한이 있을텐데 대량의 파일을 svn에 추가했을 때 제대로 작동할 지 모르겠다.


참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.