
이제 스테이지에 올린 개체들을 커밋하여 상태에 대한 스냅샷을 남기면 됨
스테이지 개체들을 담는 트리 개체를 만들고, 그 개체를 커밋 개체가 가리키는 것
즉 커밋 개체는 트리 개체를 가리키는 개체임
커맨드는 commit -m “메시지”로 편의상 메세지를 미리 받는 형식으로 진행
// geet/commands/porcelain/geetCommit.kt
package geet.commands.porcelain
import geet.exceptions.BadRequest
import geet.objects.GeetCommit
import geet.objects.GeetTree
import geet.utils.commandutil.plumbingutil.saveObjectInGeet
import geet.utils.commandutil.porcelainutil.editCurrentRefContent
import geet.utils.indexManager
fun geetCommit(commandLines: Array<String>): Unit {
if (commandLines.size != 3 || commandLines[1] != "-m") { // 입력 형식 검사
throw BadRequest("\'commit -m \"커밋 메시지\"\'와 같은 형식으로 입력해주세요.")
}
val indexData = indexManager.getIndexFileData()
val stageObjects = indexData.stagingArea.map { it.blobObject } // 스테이지에 올라와있는 개체들의 리스트
val treeObject = GeetTree(objects = stageObjects as MutableList) // 그 리스트로 트리 개체 생성
saveObjectInGeet(treeObject) // 트리 개체 깃 저장소에 저장
val commitObject = GeetCommit( // 커밋 개체 생성
tree = treeObject.hashString, // 가리키는 트리 해시값
parent = indexData.lastCommitHash, // 부모 커밋 해시값
message = commandLines[2], // 커밋 메시지
)
saveObjectInGeet(commitObject) // 커밋 개체 깃 저장소에 저장
indexData.lastCommitHash = commitObject.hashString // index 파일에 최근 커밋 해시값 수정
indexData.stagingArea.clear() // 스테이지 비움
indexManager.writeIndexFile() // 인덱스 파일 저장
editCurrentRefContent(commitObject.hashString) // 현재 ref의 내용을 현재 커밋의 해시값으로 수정
}
원래 인덱스 파일에 커밋이 가리키는 트리 해시값을 lastCommitTreeHash 라는 프로퍼티로 저장하고 있었는데, 그렇게 하면 커밋이 많아진 경우 부모 커밋에 대한 정보가 부족해 작업 디렉토리 복원이 어려워 비교하기가 어려웠음
즉, 커밋 기록들을 기반으로 부모 커밋을 따라가면서 작업 디렉토리를 복원하는데, 부모 커밋 정보가 없는 트리 해시값으로는 최근 커밋의 변경 사항만 가지고 있고 작업 디렉토리 전체의 내용은 알 수 없으므로 어느 것이 새로운 파일인지, 수정된 파일인지 비교가 힘들었던 것
그래서 아예 커밋의 해시값을 lastCommitHash라는 프로퍼티로 저장하는 방향으로 수정
ref는 브랜치 역할
.git/HEAD에 ref: refs/heads/master 이런 식으로 저장 됨
또한 .git/refs/heads 에 브랜치 이름(예 master)라는 이름으로 저장되며, 내용으로 브랜치가 가리키는 커밋 해시값을 가짐
// geet/utils/commandutil/procelainutil/commitUtil.kt
package geet.utils.commandutil.porcelainutil
import geet.utils.GEET_DIR_PATH
import geet.utils.GEET_HEAD_FILE_PATH
import geet.utils.compressToZlib
import java.io.File
fun getCurrentRef(): String { // 현재 ref가 어떤 것인지 가져오기
val headFile = File(GEET_HEAD_FILE_PATH) // .geet/HEAD 파일 가져오기
if (!headFile.exists()) {
throw NotFoune("HEAD 파일이 존재하지 않습니다.\n저장소가 초기화가 되었는지 확인해 주세요.")
}
return headFile.readText().trim().split(" ")[1] // refs/heads/master만 가져옴
}
fun editCurrentRefContent(content: String) {
val refFile = File("${GEET_DIR_PATH}/${getCurrentRef()}") // .geet/refs/heads/master 파일
if (!refFile.exists()) { 존재하지 않는 경우 생성
refFile.createNewFile()
}
refFile.writeText(content) // 내용(커밋 개체 해시값) 작성
}
이렇게 까지 하고 쉽게 끝난다고 생각했는데 add, status 명령어에서 문제가 발생함
일단 커밋 해시값을 기반으로 작업 디렉토리를 복원하기 위한 개체들을 가져오는 함수를 구현함
// geet/util/geetUtil.kt
..
fun getObjectsFromCommit(commitHash: String?): List<GeetObject> {
if (commitHash == null) { // 해시값이 null이라면(최근 커밋이 없는 경우) 빈 리스트 반환
return listOf()
}
val objects = mutableListOf<GeetObject>()
val commitContents = getObjectContents(commitHash) // 커밋 개체 내용 가져오기
// 커밋 개체 내용에는 줄로 구분되며 가리키는 트리 해시값, 부모 커밋 해시값, 개체 생성 시간, 커밋 메시지 정보가 있음
val splitContents = commitContents.trim().split(("\n")) // 줄로 구분하여 스플릿
splitContents.forEach { line ->
if (line == "") { // 빈 줄이면 리턴
return@forEach
}
// 'tree <해시값>' 처럼 저장된 정보를 띄어쓰기로 잘라냄
val splitLine = line.split(" ")
if (splitLine.size < 2) { // 사이즈가 다르면 리턴
return@forEach
}
val prop = splitLine[0]
val value = splitLine[1]
when (prop) {
"tree" -> {
getObjectsFromTree(value).forEach {
objects.add(it)
}
}
"parent" -> {
getObjectsFromCommit(value).forEach {
// 부모 커밋 개체로 재귀, 새로운 파일명의 개체라면 추가
val samePathObject = objects.find { geetObject ->
geetObject.path == it.path
}
// 부모 커밋으로 거슬러 올라가기 전이 항상 최근 파일의 상태의 해시값임
// 그러므로 거슬러 올라가면서 같은 내용의 파일이 있으면 추가하지 않고, 없으면 추가하면 됨
if (samePathObject == null) {
objects.add(it)
}
}
}
else -> return@forEach
}
}
return objects
}
그 후 이 복원된 개체로 add, status시 비교하여 추가 및 출력을 해주도록 수정해줌