티스토리 뷰

GitHub: https://github.com/ohksj77/kgit

 

GitHub - ohksj77/kgit: Kotlin으로 만든 git 프로젝트

Kotlin으로 만든 git 프로젝트. Contribute to ohksj77/kgit development by creating an account on GitHub.

github.com

전체 코드는 위 Repository에서 확인할 수 있습니다. 이 포스트에서 전체 구현을 다루지는 않습니다.

 

들어가며

KGit은 Kotlin으로 구현된 Git 클론 프로젝트입니다. 실제 Git과 유사한 CLI 명령어를 제공하며, Git의 핵심 개념인 객체 모델과 참조 시스템을 구현했습니다. Git 내부 동작을 알아보고자 진행했습니다.

 

동작 예시

 

프로젝트 구조

kgit/
├── src/main/kotlin/
│   ├── command/          # 명령어 처리 로직
│   │   ├── Command.kt    # 명령어 인터페이스 및 구현체들
│   │   ├── CommandFactory.kt  # Factory 패턴으로 명령어 생성
│   │   └── CommandType.kt     # 지원하는 명령어 타입 정의
│   ├── object/           # Git 객체 모델 (Blob, Tree, Commit, Tag)
│   │   ├── KgitObject.kt      # 기본 객체 구조 및 타입 정의
│   │   ├── HashObject.kt      # 객체 해시 생성 및 저장
│   │   ├── Tree.kt            # 트리 객체 구조
│   │   ├── WriteTree.kt       # 트리 객체 생성
│   │   ├── LsTree.kt          # 트리 내용 출력
│   │   ├── CommitTree.kt      # 커밋 객체 생성
│   │   ├── Tag.kt             # 태그 객체 생성
│   │   ├── CatFile.kt         # 객체 내용 확인
│   │   ├── UpdateIndex.kt     # 인덱스 업데이트
│   │   └── GitMode.kt         # 파일 모드 정의
│   ├── porcelain/        # 사용자 친화적 명령어
│   │   ├── RevParse.kt        # 참조 해석
│   │   └── Tag.kt             # 태그 명령어
│   ├── reference/        # 참조 시스템 (HEAD, 태그 등)
│   │   ├── SymbolicRef.kt     # 심볼릭 참조 처리
│   │   └── UpdateRef.kt       # 참조 업데이트
│   ├── config/           # 설정 관리
│   │   └── Config.kt          # 설정 파일 관리
│   └── Main.kt          # 진입점
├── build.gradle.kts     # Gradle 빌드 설정
├── kgit.sh              # 실행 스크립트
└── README.md

 

핵심 기능

1. Git 객체 모델

KGit은 Git의 4가지 기본 객체 타입을 모두 구현했습니다:

Blob (파일 데이터)

enum class Type(val value: String) {
    BLOB("blob"),
    TREE("tree"),
    COMMIT("commit"),
    TAG("tag");
}

Tree (디렉토리 구조)

@Serializable
data class TreeEntry(
    val mode: Long = 0L,
    val file: String,
    val objectHash: String = ""
) {
    fun isDirectory(): Boolean = mode == GitMode.TREE_MODE
    fun isSymlink(): Boolean = mode == GitMode.SYMLINK_MODE
    fun isExecutable(): Boolean = mode == GitMode.EXECUTABLE_BLOB_MODE
    fun isRegularFile(): Boolean = !isDirectory() && !isSymlink() && mode != GitMode.SUBMODULE_MODE
}

Commit (커밋 정보)

@Serializable
data class CommitObjectBody(
    val tree: String,
    val parent: List<String>,
    val author: String,
    val date: String,
    val message: String
)

Tag (태그 정보)

@Serializable
data class TagBody(
    val `object`: String,
    val type: Type,
    val tag: String,
    val tagger: String
)

 

2. 객체 저장 시스템

Git과 동일한 방식으로 SHA-1 해시를 사용한 객체 저장:

fun newKey(typeVal: Type, data: ByteArray): String {
    val str = newContent(typeVal, data)
    val digest = MessageDigest.getInstance("SHA-1")
    digest.update(str)
    return digest.digest().joinToString("") { "%02x".format(it) }
}

fun newContent(typeVal: Type, data: ByteArray): ByteArray {
    val header = "${typeVal.value} ${data.size}\\\\u0000".toByteArray()
    val output = ByteArrayOutputStream()
    output.write(header)
    output.write(data)
    return output.toByteArray()
}
  • 객체는 .kgit/objects/ 디렉토리에 저장
  • 첫 2글자로 서브디렉토리 분할 (예: a1/b2c3d4...)
  • zlib 압축으로 저장 공간 절약

 

3. 명령어 시스템

Factory 패턴을 사용한 명령어 처리:

object CommandFactory {
    private val commandMap: EnumMap<CommandType, (String) -> Command> = EnumMap(CommandType::class.java)

    init {
        commandMap[CommandType.INIT] = { kgitDir -> InitCommand(kgitDir) }
        commandMap[CommandType.ADD] = { kgitDir -> AddCommand(kgitDir) }
        commandMap[CommandType.LS] = { kgitDir -> LsCommand(kgitDir) }
        commandMap[CommandType.CAT] = { kgitDir -> CatCommand(kgitDir) }
        commandMap[CommandType.WRITE] = { kgitDir -> WriteCommand(kgitDir) }
        commandMap[CommandType.COMMIT] = { kgitDir -> CommitCommand(kgitDir) }
        commandMap[CommandType.TAG] = { kgitDir -> TagCommand(kgitDir) }
    }

    fun getCommand(commandName: String, kgitDir: String): Command {
        val type = CommandType.from(commandName)
        return commandMap[type]?.invoke(kgitDir)
            ?: throw IllegalArgumentException("지원하지 않는 명령입니다: $commandName")
    }
}

 

지원하는 명령어

기본 명령어

  • init: 저장소 초기화 (.kgit 디렉토리 및 하위 구조 생성)
  • add <파일명>: 스테이징 영역에 파일 추가 (인덱스 업데이트)
  • write: 트리 객체 생성 (현재 인덱스 기반)
  • ls <트리해시>: 트리 구조 출력 (재귀적 출력 지원)
  • commit <트리해시> "메시지": 커밋 객체 생성
  • cat pretty-print <해시>: 객체 내용 확인
  • tag <태그명> <해시> "메시지": 태그 생성

 

사용 예시

# 저장소 초기화
./kgit.sh init

# 파일 추가 및 커밋
./kgit.sh add example.txt
./kgit.sh write
./kgit.sh commit <트리해시> "첫 번째 커밋"

# 태그 생성
./kgit.sh tag v1.0 <커밋해시> "릴리즈 v1.0"

# 객체 내용 확인
./kgit.sh cat pretty-print <해시>

 

기술적 특징

1. Kotlin 기능 활용

  • Data Class: 불변 객체 모델링으로 타입 안전성 보장
  • Sealed Class: 타입 안전한 열거형과 패턴 매칭
  • Extension Functions: 유틸리티 함수 확장으로 가독성 향상
  • Result Type: 명시적 에러 처리 패턴으로 예외 상황 관리
  • Kotlinx Serialization: JSON 직렬화로 설정 및 객체 저장

 

2. 함수형 프로그래밍

fun parseObject(kgitDir: String, objectHash: String): Result<KgitObject> {
    val path = objectHash.path(kgitDir)
    val file = File(path)

    if (!file.exists()) {
        return Result.failure(FileNotFoundException("Object file not found at $path"))
    }

    return try {
        val data = file.readBytes()
        unmarshalObject(data)
    } catch (e: Exception) {
        Result.failure(e)
    }
}

 

3. 에러 처리

  • Result<T> 타입을 사용한 명시적 에러 처리
  • 예외 상황에 대한 적절한 메시지 제공
  • 타입 안전한 에러 전파로 런타임 에러 방지

 

Git 모드 시스템

파일 권한과 타입을 표현하는 모드 시스템:

object GitMode {
    // 디렉토리 (Tree)
    const val TREE_MODE: Long = 0b010000000000000000000L // 0o040000

    // 일반 파일 (Blob)
    const val BLOB_MODE: Long = 0b00110001001010010101010101010101L // 0o100644

    // 실행 가능한 일반 파일 (Blob)
    const val EXECUTABLE_BLOB_MODE: Long = 0b00110001111011010101101101101L // 0o100755

    // 심볼릭 링크 (Blob)
    const val SYMLINK_MODE: Long = 0b101000000000000000000L // 0o120000

    // 서브모듈 (Commit)
    const val SUBMODULE_MODE: Long = 0b111000000000000000000L // 0o160000
}

 

참조 시스템

Git의 참조 시스템을 구현하여 HEAD, 태그, 브랜치 관리:

enum class SymbolicRefType {
    HEAD,
    FETCH_HEAD,
    ORIG_HEAD,
    MERGE_HEAD;

    companion object {
        fun fromString(typeString: String): SymbolicRefType? {
            return entries.firstOrNull { it.name == typeString }
        }
    }
}

 

설정 시스템

JSON 기반 설정 파일 관리:

@Serializable
data class Config(
    val core: Core,
    val user: User
) {
    fun createConfigFile(kgitDir: String): Result<Unit> {
        val configFile = File(kgitDir, CONFIG_FILE_NAME)
        return try {
            val jsonString = Json.encodeToString(Config.serializer(), this)
            configFile.writeText(jsonString)
            Result.success(Unit)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

 

빌드 및 실행

요구사항

  • Kotlin 2.1.20+
  • Java 17+
  • Gradle

 

학습 포인트

1. Git 내부 구조 이해

  • 객체 모델의 실제 구현: Blob, Tree, Commit, Tag의 구체적 구현
  • SHA-1 해시 기반 저장 시스템: Git의 핵심 저장 방식 이해
  • 참조와 심볼릭 링크의 동작: HEAD, 태그 등의 참조 시스템

2. Kotlin 기능

  • 함수형 프로그래밍 패턴: Result 타입, 고차 함수 활용
  • 타입 안전한 에러 처리: 예외 상황의 명시적 관리
  • 현대적인 직렬화 라이브러리 활용: JSON 기반 설정 및 객체 저장

3. 시스템 프로그래밍

  • 파일 시스템 조작: 디렉토리 생성, 파일 읽기/쓰기
  • 바이너리 데이터 처리: 압축/압축 해제, 해시 계산
  • 압축 알고리즘 활용: zlib을 통한 저장 공간 최적화

4. 설계 패턴

  • Factory 패턴: 명령어 객체 생성
  • Strategy 패턴: 다양한 명령어 처리 방식
  • Result 패턴: 에러 처리의 일관성

 

개선 가능한 부분

  1. 브랜치 기능: 현재는 기본적인 커밋만 지원, 브랜치 관리 기능 추가
  2. 병합 기능: 여러 커밋 간의 병합 로직 구현
  3. 원격 저장소: 네트워크 통신을 통한 원격 저장소 지원
  4. 성능 최적화: 대용량 저장소에서의 성능 개선
  5. 테스트 커버리지: 단위 테스트 및 통합 테스트 추가

 

후기

  • 그냥 사용만 하던 Git 시스템에 대해 몰랐던 동작들을 알 수 있어 좋았습니다.
  • GitHub 과 같은 remote repository에 연동해보고 싶은 마음도 있었으나 많은 고난(?)이 예상되어서 시도하지 못했습니다. 언젠가 시도해보고 싶습니다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함