[Git 만들어보기 - Geet] branch 명령어로 브랜치 다루기

송준섭 Junseop Song·2024년 3월 18일

Git 만들어보기 - Geet

목록 보기
18/21
post-thumbnail

2024-03-01 구현

branch 명령어로 브랜치를 추가 또는 삭제를 하거나 브랜치 목록을 출력할 수 있음

// geet/command/geetBranch.kt
package geet.command

import geet.exception.BadRequest
import geet.exception.NotFound
import geet.util.const.*

data class BranchCommandOptions(  // 사용자가 입력한 옵션 정보
    val branchName: String? = null,
    val delete: Boolean = false,
)

fun geetBranch(commandLines: Array<String>): Unit {
    val options = getBranchCommandOptions(commandLines)

    if (options.branchName == null && options.delete) {  // -d 옵션을 브랜치 이름과 함께 사용하지 않은 경우
        throw BadRequest("삭제하기 위한 브랜치 이름이 없습니다.")
    }

    if (options.branchName == null) {  // 만약 브랜치 이름이 없다면 브랜치 목록 출력
        printBranchList()
        return
    }

    if (options.delete) {  // 브랜치 삭제
        branchManager.deleteBranch(options.branchName)
        return
    }

    try {  // 브랜치 이름만 적은 경우 브랜치 생성
        branchManager.createBranch(options.branchName)
    } catch (exception: NotFound) {  // 중간에 에러가 발생하면(현재 브랜치에서 첫 커밋을 하지 않아 커밋 해시가 NotFound라면)
        throw BadRequest("첫 커밋을 하기 전에는 브랜치를 생성할 수 없습니다.")
    }
}

fun printBranchList(): Unit {
    val headBranchName = headManager.getHeadBranchName()
    branchManager.getAllBranchNames().forEach {
        if (it == headBranchName) {
            println("${yellow}${it} *${resetColor}")
        } else {
            println(it)
        }
    }
}

fun getBranchCommandOptions(commandLines: Array<String>): BranchCommandOptions {
    if (commandLines.size == 1) {
        return BranchCommandOptions()
    }

    if (commandLines.size == 2) {
        return BranchCommandOptions(branchName = commandLines[1])
    }

    if (commandLines.size == 3 && (commandLines[1] == "-d" || commandLines[2] == "-d")) {
        val branchName = if (commandLines[1] == "-d") commandLines[2] else commandLines[1]
        return BranchCommandOptions(branchName = branchName, delete = true)
    }

    throw BadRequest("branch 명령에 대하여 잘못된 입력입니다.: ${red}${commandLines.joinToString(" ")}${resetColor}")
}

그리고 이 branch들을 다루는 BranchManager도 추가해주었다.

// geet/manager/BranchManager.kt
package geet.manager

import geet.exception.BadRequest
import geet.exception.NotFound
import geet.util.const.headManager
import geet.util.const.red
import geet.util.const.resetColor
import java.io.File

class BranchManager {

    val refsDir = File(".geet/refs")

    fun getAllBranchNames(): List<String> {  // 모든 브랜치 이름 가져오기
        val branchDir = File(refsDir, "heads")

        return branchDir.walkTopDown()
            .filter { it.isFile }
            .map { it.relativeTo(branchDir).path }
            .toList()
    }

    fun getBranchCommitHash(branchName: String): String {  // 브랜치 이름으로 해당 브랜치가 가리키는 커밋 해시값 가져오기
        val branchFile = File(refsDir, "heads/$branchName")
        if (!branchFile.exists()) {
            throw NotFound("브랜치가 존재하지 않습니다.: ${red}${branchName}${resetColor}")
        }

        return branchFile.readText()
    }

    fun setBranchCommitHash(branchName: String, commitHash: String) {  // 브랜치가 가리키는 커밋 변경
        val branchFile = File(refsDir, "heads/$branchName")
        if (!branchFile.exists()) {
            throw NotFound("브랜치가 존재하지 않습니다.: ${red}${branchName}${resetColor}")
        }

        branchFile.writeText(commitHash)
    }

    fun createBranch(branchName: String) {  // 브랜치 생성
        if (File(refsDir, "heads/$branchName").exists()) {
            throw BadRequest("이미 존재하는 브랜치입니다.: ${red}${branchName}${resetColor}")
        }

        if (branchName.contains("/")) {
            branchName.split("/").subList(0, branchName.split("/").size - 1).forEachIndexed { index, _ ->
                val dirName = branchName.split("/").subList(0, index + 1).joinToString("/")
                File(refsDir, "heads/${dirName}").mkdir()
            }
        }

        val headBranchName = headManager.getHeadBranchName()
        val headCommitHash = getBranchCommitHash(headBranchName)
        File(refsDir, "heads/$branchName").createNewFile()
        setBranchCommitHash(branchName, headCommitHash)
    }

    fun switchBranch(branchName: String) {  // 브랜치 변경
        if (!File(refsDir, "heads/$branchName").exists()) {
            throw NotFound("브랜치가 존재하지 않습니다.: ${red}${branchName}${resetColor}")
        }

        headManager.setHead(branchName)
				// TODO: 브랜치가 가리키는 커밋 상태로 복구
    }

    fun deleteBranch(branchName: String) {  // 브랜치 삭제
        val branchFile = File(refsDir, "heads/$branchName")
        if (!branchFile.exists()) {
            throw NotFound("이미 존재하지 않는 브랜치입니다.: ${red}${branchName}${resetColor}")
        }

        branchFile.delete()
    }
}

여기서 switchBranch 함수는 아직 구현이 부족한 상태이다.

커밋이 가리키는 트리의 파일들을 복구하는 로직은 commit 명령어 구현 후에 다시 구현할 예정

0개의 댓글