
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 명령어 구현 후에 다시 구현할 예정