깃허브 파종기를 만들어 보자!

2025. 2. 24. 18:43개발자 과정/Kotlin

파종기: 씨앗등을 자동으로 심는 기계

 

깃허브에선 기여도(contribution)라는 것이 있다.

그리고 이것을 우리는 잔디심기 라고 표현한다. 

 

근데 문득 그런 생각이 들었다. 잔디가 많이 심어져 있는 것을 미덕으로 생각한다면, 

진짜 "잔디만" 잔뜩 심어진 것은 미덕일까? 그럼 자동으로 잔디를 심어주는 봇을 만든다면 어떨까?

사실 말도 안되는 소리긴 하지만, 재밌을거 같으니 일단 만들어나 봤다.

 

1. 우선 더미 리포지토리를 하나 생성한다. 

 

 

2. 해당 리포지토리를 클론해 로컬로 받아온다.

 

3. 봇을 구현한다.

main

class RunnablePlanter(
    directory:String
){
    private val modify=Modify("$directory/contribute.kt")
    private val gitController=GitController(directory)

    fun runnable() = runBlocking {
        launch {
            while (true) {
                try {
                    modify.modifyFile()
                    gitController.gitProcess()
                    println("다음 실행까지 대기 중... (12시간)")
                    delay(12 * 60 * 60 * 1000L) // 12시간 대기
                } catch (e: Exception) {
                    println("오류 발생: ${e.message}")
                }
            }
        }
    }
}


fun main(){
    val runnableGitcro=RunnablePlanter(System.getenv("DIRECTORY"))
    runnableGitcro.runnable()
}

백그라운드에서 돌아가야 하기에, 코루틴을 사용한다. 전체적인 로직의 흐름을 이곳에서 관리한다.

 

Modify

class Modify(
    private val filePath: String
) {
    private fun generateRandomString(count: Int = 30): String {
        return (1..count)
            .joinToString("\n\n") {
                CodeDummy.dummyCodeSnippets.random()
            }
    }

    fun modifyFile() {
        val file = File(filePath)
        val now = LocalDateTime.now()
        val randomText = generateRandomString()

        // 파일 내용을 지우고 새로 쓰기
        file.writeText("$now\n[$randomText]")

        println("파일 수정 완료: $now\n[$randomText]")
    }
}

클론받은 더미 리포지토리의 kt파일을 열어서 무작위 더미 코드를 넣는 다. count를 통해 어느만큼 채워 넣을 것인지 조절할 수 있다.

 

CodeDummy

CodeDummy.kt
0.00MB

더미 코드는 길어서 그냥 첨부해 올린다.

GitController

class GitController(
    private val repoPath: String
) {
    fun gitProcess() {
        val processCommands = listOf(
            "git fetch origin",
            "git checkout -B edit", // 새 브랜치 생성 (기존에 있으면 덮어씀)
            "git add --renormalize .",
            "git commit -m \"오늘의 코드: ${LocalDateTime.now()}\"",
            "git push origin edit --force"
        )

        processCommands.forEach { command ->
            val process = ProcessBuilder("cmd", "/c", command) // Linux/Mac용 (Windows는 "cmd", "/c" 사용)
                .directory(File(repoPath)) // 실행 경로 지정
                .redirectErrorStream(true)
                .start()

            process.inputStream.bufferedReader().use { reader ->
                reader.lines().forEach { println(it) } // 실행 로그 출력
            }

            process.waitFor()
        }

        println("//////////////////////////////////////////////////\n" +
            "PUSH 완료! GitHub Action Trigger")
    }
}

커멘드를 통해 깃허브에 코드를 푸시하는 기능을 한다. 

커멘드 라인을 잘 보면, 브랜치를 생성하여 그곳에다 푸시를 하는 것을 볼 수 있다. 

이것은 보다 자연스러운 구조를 만들어, 자동화 감지를 피해보려는 의도이다.

 

4. 워크플로우를 작성한다

파일을 더미코드를 채워 수정하고, 브랜치를 생성하여 푸시했다면, 

해당 브랜치를 통해 트리거를 작동하는 워크 플로우가 필요하다. 

name: Auto Merge Edit to Main

on:
  push:
    branches:
      - edit  # edit 브랜치에 push 발생 시 실행

permissions:
  contents: write
  pull-requests: write

jobs:
  merge:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 전체 히스토리 가져오기

      - name: Configure Git
        run: |
          git config --global user.name ""
          git config --global user.email ""

      - name: Authenticate GitHub Token
        run: |
          git remote set-url origin https://x-access-token:${{ secrets.TOKEN }}@"URL"

      - name: Force Sync Main with Edit
        run: |
          git fetch origin
          git checkout edit
          git branch -f main edit  # main 브랜치를 edit과 동일하게 만듦
          git checkout main
          git push --force origin main  # main 브랜치를 강제로 덮어쓰기

edit브랜치에서 푸시가 감지되면, main 브랜치는 edit을 기준으로 하여 강제 덮어쓰기 한다.

 

5. 마무리

기존에 인터넷에 있는 방법들은 워크 플로우의 스케줄러를 사용하여 더미 커밋을 하는 것인데,

이 봇은 직접 코드 스니펫을 작성하여 실제 코드 작성처럼 보이는 눈속임을 했다는 것에 의의가 있다.

 

역시 하등 도움 안는거 할때가 제일 재밌다.