[Git 만들어보기 - Geet] geet restore 명령어로 파일 복원하기

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

Git 만들어보기 - Geet

목록 보기
13/21
post-thumbnail

2024-02-25 구현

restore는 reset과 달리 HEAD를 건드리지 않고 파일 그 자체만 특정 상태로 복원하는 역할을 한다.

—worktree, —staged, —source 세 가지 옵션을 구현할 예정으로, 다음과 같이 입력된 커맨드 라인으로부터 옵션들을 뽑아왔다.

// geet/commands/porcelain/geetRestore.kt
package geet.commands.porcelain

import geet.exceptions.BadRequest
import geet.utils.commandutil.porcelainutil.getSourceCommitHash
import geet.utils.commandutil.porcelainutil.isSourceOption

data class GeetRestoreOptions(
    var worktree: Boolean = true,
    var staged: Boolean = false,
    var sourceCommitHash: String? = null,
    val fileName: String,
)

fun geetRestore(commandLines: Array<String>): Unit {
    val geetRestoreOptions = getGeetRestoreOptions(commandLines)
    println(geetRestoreOptions)
}

fun getGeetRestoreOptions(commandLines: Array<String>): GeetRestoreOptions {
    if (commandLines.size == 1) {
        throw BadRequest("파일 명이 입력되지 않았습니다.")
    }

    if (commandLines.size == 2) {
        return GeetRestoreOptions(fileName = commandLines[1])
    }

    if (commandLines.size == 3) {
        val fileName = commandLines[2]

        val option = commandLines[1]

        if (isSourceOption(option)) {
            return GeetRestoreOptions(worktree = false, sourceCommitHash = getSourceCommitHash(option), fileName = fileName)
        }

        when (option) {
            "--worktree" -> return GeetRestoreOptions(fileName = fileName)
            "--staged" -> return GeetRestoreOptions(worktree = false, staged = true, fileName = fileName)
            else -> throw BadRequest("옵션이 올바르지 않습니다.: ${commandLines[1]}")
        }
    }

    if (commandLines.size == 4) {
        if ((commandLines[1] == "--worktree" && commandLines[2] == "--staged") ||
            (commandLines[1] == "--staged" && commandLines[2] == "--worktree")) {
            return GeetRestoreOptions(worktree = true, staged = true, fileName = commandLines[3])
        }
    }

    throw BadRequest("옵션이 올바르지 않습니다.")
}
// geet/utils/commandutil/porcelainutil/restoreUtil.kt
package geet.utils.commandutil.porcelainutil

fun isSourceOption(option: String): Boolean {  // 정규표현식으로 source 옵션인지 확인
    val regex = Regex("^--source=HEAD~[0-9]+")
    if (!regex.matches(option)) {
        return false
    }
    return true
}

fun getSourceCommitHash(option: String): String {  // 입력된 source의 커밋 해시값 가져오기
    val index = option.indexOf("~")
    val carrotCount = option.substring(index + 1).toInt()
    var commitHash = getCurrentRefCommitHash()
    for (i in 0 until carrotCount) {
        commitHash = getParentCommitFromCommitHash(commitHash)
    }
    return commitHash
}

입력될 수 있는 옵션은 다음과 같다.

  • restore <파일명> 또는 restore —worktree <파일명> 파일을 현재 커밋의 원래 상태로 복원
  • restore —staged <파일명> 스테이지에서 파일 제거
  • restore —worktree —staged <파일명> 스테이지에서 제거하고, 커밋의 원래 상태로 복원함
  • restore —source=HEAD~<돌아갈 숫자> <파일명> HEAD로부터 숫자만큼 부모 커밋으로 돌아가 그 커밋 상태로 파일을 복원함

원래 파일명에 ‘.’와 같이 폴더를 넣을수도 있으나 목적은 restore 명령어 자체를 공부하는 것이므로 해주지 않았다.

또한 여러 옵션을 섞어서 쓸 수도 있지만 그것도 고려해주지 않았다.

구현한 restore 함수는 아래와 같다.

// geet/utils/commandutil/porcelainutil/restoreUtil.kt

fun restore(geetRestoreOptions: GeetRestoreOptions) {
		// worktree, staged 동시 요청인 경우
    if (geetRestoreOptions.worktree && geetRestoreOptions.staged) {
        restoreToWorktree(geetRestoreOptions.fileName)
        restoreStage(geetRestoreOptions.fileName)
    }

		// worktree 요청인 경우
    if (geetRestoreOptions.worktree && !geetRestoreOptions.staged) {
        restoreToWorktree(geetRestoreOptions.fileName)
    }

		// staged 요청인 경우
    if (geetRestoreOptions.staged && !geetRestoreOptions.worktree) {
        restoreStage(geetRestoreOptions.fileName)
    }

		// source 요청인 경우
    if (geetRestoreOptions.sourceCommitHash != null) {
        restoreToWorktree(geetRestoreOptions.fileName, geetRestoreOptions.sourceCommitHash!!)
    }
}

// 
fun restoreToWorktree(fileName: String, commitHash: String = getCurrentRefCommitHash()) {
    getObjectsFromCommit(commitHash).forEach {  // 해당 커밋으로 개체들을 복원, 해당 파일 내용으로 수정
        if (getRelativePath(it.path) == getRelativePath(fileName)) {
            val file = File(it.path)
            file.writeText(it.content)
        }
    }
}

fun restoreStage(fileName: String) {  // 스테이지에서 제거
    indexManager.removeObjectFromStagingArea(GeetBlob(fileName, "removed"))
    indexManager.writeIndexFile()
}

0개의 댓글