우아한 테크코스 8기 오픈미션 보고서

이 글의 목차46개
  1. 1.1 프로젝트 목표
  2. 1.2 지원 명령어 및 기능 요약
  3. 기본 저장소 관리
  4. git init
  5. git add …
  6. git commit
  7. 상태 조회 및 히스토리
  8. git status
  9. git log
  10. 브랜치 및 브랜치 전환/병합
  11. git branch
  12. git checkout
  13. git merge (Fast-Forward only)
  14. 로컬 디렉터리 기반 Remote 연동
  15. git push / git pull / git clone
  16. 2.1 전체 아키텍처
  17. Application 계층
  18. Controller / View 계층
  19. Service 계층
  20. Repository / Remote 계층
  21. Domain 계층
  22. 2.2 Git 도메인 모델 핵심
  23. Blob
  24. Tree
  25. Commit
  26. Index
  27. Head
  28. 2.3 .javaGit 저장 구조와 Repository 요약
  29. 디렉터리/파일 구조
  30. Repository 역할 요약
  31. 2.4 명령어 흐름 개요 (입력 → 도메인/저장소)
  32. 입력 처리
  33. 주요 명령 흐름 요약
  34. 3.1 git init – 저장소 초기화
  35. 3.2 git add – 파일 스테이징
  36. 3.3 git commit – 스냅샷 확정
  37. 3.4 git status – 상태 조회
  38. 3.5 git log – 커밋 로그 조회
  39. 3.6 git branch / git checkout – 브랜치 관리와 전환
  40. 3.7 git merge – Fast-Forward 병합
  41. 3.8 git push / git pull / git clone – 로컬 디렉터리 기반 Remote
  42. 3.9 git serve-http / git push-http / git pull-http – HTTP Remote
  43. 4.1 ErrorCode 기반 표준 에러 포맷
  44. 4.2 도메인 계층 검증
  45. 4.3 Repository 계층 예외 처리
  46. 4.4 서비스 계층 예외 처리

1. 프로젝트 개요 (Overview)

1.1 프로젝트 목표

이 프로젝트는 순수 Java로 Git의 핵심 기능을 재구현한 CLI 도구이다.

기존 Git을 다음과 같은 내부 구조와 동작을 직접 구현하는 것을 목표로 한다.

  • Git 내부 객체 모델 직접 구현
  • Blob / Tree / Commit / Ref / Index 를 도메인 객체로 정의하고, 이를 통해 Git이 파일 스냅샷과 히스토리를 관리하는 방식을 그대로 재현한다.
  • 각 도메인은 불변(immutable)을 지향하고, 생성 시점에 철저한 검증을 수행한다.
  • 파일 시스템 기반 Git 저장소 구현
  • 실제 Git이 .git 디렉터리에 메타데이터를 저장하듯, 이 프로젝트는 .javaGit 디렉터리를 만들어 동일한 역할을 수행한다.
  • Git 객체는 SHA-1 해시 기반으로 저장된다.
  • 각 계층(Controller / Service / Repository / Domain)을 명확히 나누어, 아키텍처 관점에서 Git을 이해할 수 있도록 설계한다.


1.2 지원 명령어 및 기능 요약

구현된 명령어는 다음과 같다.

기본 저장소 관리

git init

  • 현재 작업 디렉터리 기준으로 .javaGit 디렉터리를 생성하고, objects, refs/heads, HEAD, index 파일을 초기화한다.

git add

  • 지정한 파일들을 읽어 Blob 객체로 변환하고, 그 해시를 .javaGit/objects에 저장한다.
  • 파일 경로와 Blob 해시를 .javaGit/index에 기록하여 스테이징 영역(Index) 을 구성한다.

git commit

  • Index에 스테이징된 파일들을 기반으로 Tree 객체와 Commit 객체를 생성한다.
  • 현재 브랜치의 HEAD를 새 Commit으로 갱신하여, 히스토리를 확정한다.

상태 조회 및 히스토리

git status

  • 워킹 트리, Index, HEAD 커밋을 비교하여 추가/수정/삭제된 파일 상태를 계산하고 출력한다.

git log

  • HEAD 커밋부터 부모 커밋 체인을 따라 내려가며 커밋 히스토리를 시간 순서대로 출력한다.

브랜치 및 브랜치 전환/병합

git branch

  • 새 브랜치를 생성하거나, 존재하는 브랜치 목록을 조회한다.

git checkout

  • 브랜치를 전환한다. 현재 워킹 트리가 안전한지(StatusService 결과)를 확인한 뒤 HEAD 및 브랜치 ref를 업데이트한다.

git merge (Fast-Forward only)

  • Fast-Forward 가능한 경우에만 병합을 수행한다.
  • 현재 브랜치의 HEAD가 대상 브랜치의 조상인 경우, 단순히 HEAD를 대상 브랜치의 최신 커밋으로 이동시키는 방식으로 동작한다.

로컬 디렉터리 기반 Remote 연동

git push / git pull / git clone

  • 별도의 로컬 디렉터리를 remote 저장소처럼 간주하고, .javaGit 메타데이터(객체, refs)를 복사하는 방식으로 push/pull/clone을 구현한다.

  • clone은 .javaGit 메타데이터만 복제하고, 워킹 트리 checkout은 이후에 수행한다.

  • HTTP 기반 Remote 연동

  • git serve-http

  • HTTP 서버를 띄워, 다른 클라이언트가 HTTP API를 통해 객체/refs를 주고받을 수 있도록 한다.

  • git push-http / git pull-http (Fast-Forward only)

  • HTTP 프로토콜 위에서 push, pull을 수행하며, 이때도 Fast-Forward만 허용한다.


2. 전체 구조와 핵심 동작 개요

2.1 전체 아키텍처

이 프로젝트는 계층형 아키텍처(layered architecture) 를 따른다.

Application 계층

  • Main : 프로그램 진입점. 현재 작업 디렉터리(user.dir) 를 구해 Appconfig 에 넘긴다.
  • Appconfig : 수동 DI 컨테이너 역할을 하며, GitController, Service, Repository 인스턴스를 조립한다.

Controller / View 계층

  • GitController : CLI 입력을 받아 적절한 Command Handler 로 분기하는 중앙 컨트롤러이다.
  • command.handlers.* : InitCmd, AddCmd, CommitCmd 등 명령어별 어댑터이다.
  • OutputView : 모든 출력 메시지를 담당한다.

Service 계층

  • Git 비즈니스 로직이 위치한다. 예: InitService, AddService, CommitService, StatusService, LogService, BranchService, CheckoutService, MergeService, PushService, PullService/CloneService, HttpPushService, HttpPullService.

Repository / Remote 계층

  • FileObjectWriter/FileObjectReader : .javaGit/objects 에 Git 객체를 저장/조회한다.
  • FileIndexRepository : .javaGit/index 를 기반으로 Index 를 읽고 쓴다.
  • FileRefRepository : .javaGit/HEAD, .javaGit/refs/heads/* 로 HEAD/브랜치를 관리한다.
  • FileRemoteClient, HttpRemoteClient/Server : 로컬 디렉터리 및 HTTP remote 와의 통신을 담당한다.

Domain 계층

  • Blob, Tree, Commit, Index, Head : Git 개념을 표현하는 불변 도메인 객체이다.

2.2 Git 도메인 모델 핵심

Git 의 내부 개념을 다음 도메인 객체들로 단순하게 모델링한다.

Blob

  • 파일 내용을 byte[] 로 가진다.
  • null/빈 배열은 허용하지 않고, 방어적 복사로 불변성을 유지한다.

Tree

  • 디렉터리 스냅샷: Map<String, String> (path → blob SHA) 구조이다.
  • null/빈 키·값을 허용하지 않고, 내부 Map 은 unmodifiable 로 만든다.

Commit

  • message, treeOid, parentOid, author, createdAtMillis 를 가진다.
  • 생성 시 필수 값(message, treeOid, author)을 검증하고, 텍스트 형태로 직렬화되어 .javaGit/objects 에 저장된다.

Index

  • 스테이징 영역: Map<String, String> (path → blob SHA) 이다.
  • git add 가 이 맵을 채우고, git commit 이 이 맵을 Tree 로 변환한다.
  • 현재 체크아웃된 브랜치 참조(예: refs/heads/master) 이다.
  • .javaGit/HEAD 파일과 1:1로 대응되는 개념이다.


2.3 .javaGit 저장 구조와 Repository 요약

실제 Git 의 .git 과 비슷한 구조를 .javaGit 으로 구현한다.

디렉터리/파일 구조

  • .javaGit/objects/aa/bb… : SHA-1 해시를 앞 2글자/나머지로 나누어 객체 파일을 저장한다.
  • .javaGit/index : 각 줄이   인 텍스트 파일로, Index 상태를 담는다.
  • .javaGit/HEAD : ref: refs/heads/master 와 같은 현재 브랜치 참조이다.
  • .javaGit/refs/heads/ : 브랜치 HEAD 커밋 SHA 를 담는 파일이다.

Repository 역할 요약

  • FileObjectWriter : 바이트 배열을 SHA-1 해시 후, objects 에 저장하고 해시 문자열을 반환한다.
  • FileObjectReader : 해시로부터 raw bytes, Blob, Tree, Commit 을 복원한다.
  • FileIndexRepository : .javaGit/index 를 Index 도메인과 상호 변환한다.
  • FileRefRepository : HEAD/브랜치 ref 를 문자열 수준에서 읽고 쓴다.

2.4 명령어 흐름 개요 (입력 → 도메인/저장소)

모든 명령은 공통적으로 “CLI 입력 → GitController → Command Handler → Service → Repository/Domain → OutputView” 흐름을 따른다.

입력 처리

  • 콘솔 모드에서는 GitController.runConsole() 이 한 줄을 읽어 정규식으로 토큰화한다.
  • 첫 토큰이 “git” 인지 확인 후, 이후 토큰들을 실제 명령 인자로 사용한다.
  • run(String[] args) 가 인자를 받아 dispatch(args) 로 적절한 Command Handler 에 위임한다.

주요 명령 흐름 요약

git init

  • InitService → FileSystemInitService 가 .javaGit 디렉터리, HEAD, refs/heads/master, index 파일을 생성한다.

git add

  • AddService 가 파일을 읽어 ObjectWriter 로 Blob 저장 → Index Map 업데이트 → FileIndexRepository 로 index 파일 갱신

git commit

  • CommitService 가 Index → Tree → Commit 을 생성해 ObjectWriter 로 저장하고, FileRefRepository 로 브랜치 HEAD 를 새 커밋으로 이동시킨다.

git status

  • StatusService 가 워킹 트리 SHA, Index, HEAD Tree 를 비교해 staged/modified/deleted/untracked 상태를 계산한다.

git log

  • LogService 가 HEAD 에서 parent 체인을 따라 Commit 들을 역순으로 나열한다.
  • git branch / git checkout / git merge
  • BranchService, CheckoutService, MergeService 가 RefRepository 와 ObjectReader 를 조합해 브랜치 생성/전환/FF merge 를 수행한다.

git push / git pull / git clone / git push-http / git pull-http

  • 파일 시스템 remote(FileRemoteClient) 나 HTTP remote(HttpRemoteClient/Server) 를 통해 .javaGit/objects 와 refs 를 동기화한다.
  • 모두 Fast-Forward 조건만 허용한다.

출력

  • 모든 성공/실패 메시지, 상태/로그 출력은 OutputView 가 담당한다.


3. 주요 기능별 내부 동작

실제 Git 명령과 매칭되는 기능들을, 이 프로젝트 내부에서 어떤 순서와 로직으로 처리하는지 정리

각 기능은 대부분 “Controller → Command Handler → Service → Repository/Domain” 흐름을 따른다.


3.1 git init – 저장소 초기화

목적: 현재 디렉터리를 Git 저장소로 초기화하고, .javaGit 구조를 만든다.

흐름

  • GitController → InitCmd.execute(args) → InitService.init() → FileSystemInitService.initRepository(rootDirectoryPath)

주요 처리

  • .javaGit 디렉터리 생성
  • .javaGit/objects
  • .javaGit/refs/heads

파일 생성

  • .javaGit/HEAD 에 기본 브랜치 참조 기록: ref: refs/heads/master\n
  • .javaGit/refs/heads/master : 빈 문자열(아직 커밋 없음)
  • .javaGit/index : 빈 Index 파일 생성

검증/예외

  • 디렉터리/파일 생성 중 IO 오류 발생 시 REPOSITORY_INIT_FAILED 에러를 던진다.

3.2 git add – 파일 스테이징

목적: 워킹 트리의 변경된 파일을 읽어 Blob 으로 저장하고, Index 에 스테이징 정보(path → blob SHA) 를 기록한다.

흐름

  • GitController → AddCmd.execute(args) → AddService.add(List filePaths)

주요 처리

  1. 인자 검증
  • filePaths == null 또는 비어 있으면 ErrorCode.EMPTY_PATHS 로 예외를 던진다.
  1. 현재 Index 읽기
  • IndexRepository.read() 로 기존 스테이징 상태를 읽고, LinkedHashMap 으로 복사한다.
  1. 각 파일 처리
  • rootDirectoryPath.resolve(filePath) 로 절대 경로를 만든다.
  • 파일이 존재하지 않거나 디렉터리이면 스킵한다.
  • Files.readAllBytes 로 내용 읽기 → ObjectWriter.write(byte[]) 호출
  • FileObjectWriter 가 SHA-1 해시 계산 후 .javaGit/objects 에 저장하고, 해시를 반환한다.
  • Index Map 에 filePath → objectHash 로 업데이트한다.
  1. Index 쓰기
  • 새 Index 도메인 객체를 생성하고 IndexRepository.write(index) 로 .javaGit/index 를 갱신한다.

3.3 git commit – 스냅샷 확정

목적: Index 에 있는 스테이징 내용을 기반으로 Tree/Commit 객체를 만들고, 현재 브랜치 HEAD 를 새 커밋으로 이동시킨다.

흐름

  • GitController → CommitCmd.execute(args) → CommitService.commit(message, author)

주요 처리

  1. 메시지/작성자 검증
  • message == null || isBlank → COMMIT_MESSAGE_EMPTY
  • author == null || isBlank → COMMIT_AUTHOR_EMPTY
  1. Index 읽기 및 비어 있음 체크
  • IndexRepository.read() 로 Index 획득
  • index.stagedFiles() 가 비어 있으면 “Nothing to commit” 예외 던짐
  1. Tree 생성 및 저장
  • new Tree(index.stagedFiles()) 로 Tree 도메인 객체 생성
  • buildTreeContent(tree) 로 “blob  \n” 포맷 문자열 생성
  • ObjectWriter.write(treeContentBytes) 로 Tree 객체 저장 → treeHash 반환
  1. 현재 HEAD 커밋 읽기
  • RefRepository.readCurrentBranch() 로 현재 브랜치 이름
  • RefRepository.readBranchHead(branch) 로 HEAD 커밋 SHA
  1. Commit 생성 및 저장
  • new Commit(message, treeHash, parentHashOrNull, author) 생성
  • buildCommitContent(commit) 으로 commit 텍스트 생성
  • ObjectWriter.write(commitBytes) 로 commit 저장 → commitHash 반환
  1. HEAD 업데이트
  • RefRepository.updateBranchHead(branch, commitHash) 로 현재 브랜치 HEAD 를 새 커밋으로 갱신한다.

3.4 git status – 상태 조회

목적: 워킹 트리, Index, HEAD 커밋 Tree 를 비교해, Git 과 유사한 상태 정보를 제공한다.

흐름

  • GitController → StatusCmd.execute(args) → StatusService.status() → StatusResult

내부 동작

  1. 워킹 트리 스캔
  • Files.walkFileTree(rootDirectoryPath) 로 모든 파일 탐색
  • .javaGit 디렉터리는 SKIP_SUBTREE 로 건너뛴다.
  • 각 regular file 에 대해:
  • 상대 경로 path 계산 (\ → /)
  • 파일 내용 SHA-1 해시 계산 → Map<String, String> working 에 path → sha 저장
  1. Index 및 HEAD Tree 스냅샷 읽기
  • index = IndexRepository.read().stagedFiles()
  • headTree = readHeadTreeSnapshot() (HEAD 커밋이 없으면 빈 Map)
  1. 스테이징 영역 비교 (HEAD vs Index)
  • stagedAdded: Index 에 있고 HEAD 에 없는 경로
  • stagedModified: 둘 다 있지만 SHA 가 다른 경로
  • stagedDeleted: HEAD 에만 있는 경로
  1. 워킹 트리 vs Index 비교
  • modifiedNotStaged: 둘 다 있지만 SHA 가 다른 경로
  • deletedNotStaged: Index 에만 있고 워킹 트리에 없는 경로
  1. Untracked 계산
  • 워킹 트리에 있지만 Index 에 없는 경로를 untracked 로 분류
  1. 결과 반환
  • 위 6개 컬렉션을 StatusResult record 로 반환한다.

3.5 git log – 커밋 로그 조회

목적: 현재 브랜치 HEAD 에서 시작해 부모 체인을 따라가며 커밋 히스토리를 출력한다.

흐름

  • GitController → LogCmd.execute(args) → LogService.list() → List

내부 동작

1. HEAD 커밋 찾기

2. parent 체인을 따라가며:

  • ObjectReader.readRaw(commitHash) → commit 텍스트 파싱
  • tree, parent, author, date, message 를 읽는다.
  • dateMillis 를 ISO 문자열로 변환한다.
  • 메시지의 첫 줄만 잘라 LogEntry.message 로 사용한다. 3. 모든 커밋을 리스트에 담아 불변 리스트로 반환한다.

3.6 git branch / git checkout – 브랜치 관리와 전환

git branch

  • BranchService.list() → RefRepository.listBranches() 결과 반환.
  • BranchService.create(name):
  • 이름 검증(공백/슬래시 금지) 후 현재 브랜치 HEAD 를 base 로 createBranch 호출.

git checkout

  • CheckoutService.switchBranch(branchName):
  • 브랜치 존재 여부 확인.
  • StatusService.status() 로 워킹 트리/스테이징이 깨끗한지 확인.
  • 깨끗하면 RefRepository.updateCurrentBranch(branchName) 으로 .javaGit/HEAD 갱신.

3.7 git merge – Fast-Forward 병합

목적: 현재 브랜치에 다른 브랜치를 Fast-Forward 가능한 경우에만 병합한다.

흐름

  • GitController → MergeCmd.execute(args) → MergeService.merge(targetBranch)

주요 판단 로직

  • 대상 브랜치 이름/HEAD 가 없으면 → BRANCH_NOT_FOUND
  • 현재 HEAD 가 비어 있으면 → 대상 HEAD 로 FF (FAST_FORWARD)
  • HEAD 가 같으면 → ALREADY_UP_TO_DATE
  • 그렇지 않으면 isAncestor(currentHead, targetHead) 로 조상 여부 판단
  • 조상이면 → 현재 브랜치 HEAD 를 대상 HEAD 로 FF
  • 아니면 → NOT_FAST_FORWARD (실제 3-way merge 는 하지 않는다)

3.8 git push / git pull / git clone – 로컬 디렉터리 기반 Remote

git push (파일 시스템 remote)

  • PushService.push(remoteRoot, branch):
  • 로컬/원격 HEAD 비교, FF 조건 검사.
  • 조건 만족 시 .javaGit/objects 전체 복사 + 원격 브랜치 HEAD 를 로컬 HEAD 로 업데이트.

git pull (파일 시스템 remote)

  • PullService.pull(remoteRoot, branch):
  • 원격 HEAD 확인, FF 가능 여부 검사.
  • 가능하면 원격 객체를 로컬로 복사하고 로컬 HEAD 를 원격 HEAD 로 이동.

git clone (파일 시스템 remote)

  • CloneService.clone(remoteRoot, targetRoot):
  • target 디렉터리가 비어 있는지 확인.
  • remote .javaGit 의 objects/refs/HEAD 를 target 으로 복사 (워킹 트리는 체크아웃하지 않는다).

3.9 git serve-http / git push-http / git pull-http – HTTP Remote

git serve-http

  • HTTP 서버를 띄워, refs/objects 조회 및 업데이트를 처리한다.

git push-http (HttpPushService)

  • 원격 refs/objects 상태를 조회하고, 부족한 객체만 업로드한 뒤, FF 가능 시 원격 HEAD 를 업데이트한다.

git pull-http (HttpPullService)

  • 원격 HEAD 와 로컬 HEAD 를 비교하고, FF 가능한 경우 커밋 체인을 따라 필요한 commit/tree/blob 객체를 다운로드한 뒤 로컬 HEAD 를 이동한다.

4. 예외 처리 및 검증 전략

4.1 ErrorCode 기반 표준 에러 포맷

이 프로젝트의 예외 처리는 ErrorCode enum 을 중심으로 표준화되어 있다.

  • app.exception.ErrorCode
  • 각 항목은 [ERROR] … 형태의 메시지를 가진다.

사용 패턴

  • 도메인/서비스/리포지토리 등에서 예외 발생 시:
  • throw new IllegalArgumentException(ErrorCode.example.message());
  • SHA-1 사용 불가와 같이 시스템적인 문제는 IllegalStateException 과 함께 사용한다.
public enum ErrorCode {
    EMPTY_PATHS("[ERROR] 추가할 경로가 비어 있습니다."),
    SHA1_NOT_AVAILABLE("[ERROR] SHA-1 알고리즘을 사용할 수 없습니다."),
    FILE_IO_ERROR("[ERROR] 파일 시스템 작업 중 오류가 발생했습니다."),
    INDEX_READ_ERROR("[ERROR] 인덱스 파일을 읽는 도중 오류가 발생했습니다."),
    INDEX_WRITE_ERROR("[ERROR] 인덱스 파일을 쓰는 도중 오류가 발생했습니다."),
    OBJECT_FILE_NOT_FOUND("[ERROR] 객체 파일을 찾을 수 없습니다."),
    OBJECT_FILE_READ_FAILED("[ERROR] 객체 파일 읽기에 실패했습니다."),
    BLOB_FILE_NULL("[ERROR] Blob 내용은 null일 수 없습니다."),
    BLOB_FILE_EMPTY("[ERROR] Blob 내용은 비어있을 수 없습니다."),
    INDEX_STAGED_FILES_NULL("[ERROR] Index staged files는 null일 수 없습니다."),
    INDEX_STAGED_FILE_PATH_NULL("[ERROR] Index staged file의 경로는 null일 수 없습니다."),
    INDEX_STAGED_FILE_OID_NULL("[ERROR] Index staged file의 OID는 null일 수 없습니다."),
    INDEX_FILE_NOT_FOUND("[ERROR] Index 파일을 찾을 수 없습니다."),
    INDEX_FILE_READ_FAILED("[ERROR] Index 파일 읽기에 실패했습니다."),
    INDEX_FILE_WRITE_FAILED("[ERROR] Index 파일 쓰기에 실패했습니다."),
    OBJECT_DIRECTORY_CREATE_FAILED("[ERROR] 객체 디렉토리 생성에 실패했습니다."),
    OBJECT_FILE_WRITE_FAILED("[ERROR] 객체 파일 쓰기에 실패했습니다."),
    REPOSITORY_INIT_FAILED("[ERROR] 저장소 초기화에 실패했습니다."),
    FILE_READ_FAILED("[ERROR] 파일 읽기에 실패했습니다."),
    TREE_ENTRIES_NULL("[ERROR] Tree entries는 null일 수 없습니다."),
    TREE_ENTRY_PATH_NULL("[ERROR] Tree entry의 경로는 null일 수 없습니다."),
    TREE_ENTRY_OID_NULL("[ERROR] Tree entry의 OID는 null일 수 없습니다."),
    COMMIT_MESSAGE_NULL("[ERROR] Commit 메시지는 null일 수 없습니다."),
    COMMIT_MESSAGE_EMPTY("[ERROR] Commit 메시지는 비어있을 수 없습니다."),
    COMMIT_TREE_OID_NULL("[ERROR] Commit의 Tree OID는 null일 수 없습니다."),
    COMMIT_AUTHOR_NULL("[ERROR] Commit 작성자는 null일 수 없습니다."),
    COMMIT_AUTHOR_EMPTY("[ERROR] Commit 작성자는 비어있을 수 없습니다."),
    HEAD_REF_NULL("[ERROR] HEAD 참조는 null일 수 없습니다."),
    HEAD_REF_EMPTY("[ERROR] HEAD 참조는 비어있을 수 없습니다."),
    MALFORMED_TREE_OBJECT("[ERROR] 손상되었거나 잘못된 Tree 객체 포맷입니다."),
    MALFORMED_COMMIT_OBJECT("[ERROR] 손상되었거나 잘못된 Commit 객체 포맷입니다.");

    private final String message;

    ErrorCode(String message) {
        this.message = message;
    }

    public String message() {
        return message;
    }
}

4.2 도메인 계층 검증

도메인 객체들은 생성자에서 강력한 검증을 수행해 불변성과 일관성을 보장한다.

Blob

  • byte[] 가 null 이면 BLOB_FILE_NULL, 길이가 0 이면 BLOB_FILE_EMPTY
  • 내부에 복사본을 저장하고, 반환 시에도 복사본을 제공해 외부 수정으로부터 보호한다.

Tree

  • 입력 Map 이 null 이면 TREE_ENTRIES_NULL
  • 각 entry 의 path 가 null 이면 TREE_ENTRY_PATH_NULL, objectId 가 null 이면 TREE_ENTRY_OID_NULL
  • 내부적으로 복사 + unmodifiableMap 으로 감싼다.

Commit

  • message == null → COMMIT_MESSAGE_NULL, 공백 → COMMIT_MESSAGE_EMPTY
  • treeOid == null → COMMIT_TREE_OID_NULL.
  • author == null → COMMIT_AUTHOR_NULL, 공백 → COMMIT_AUTHOR_EMPTY

Index

  • stagedFiles == null → INDEX_STAGED_FILES_NULL
  • 각 entry 의 key/path 가 null → INDEX_STAGED_FILE_PATH_NULL
  • value/OID 가 null → INDEX_STAGED_FILE_OID_NULL

Head

  • refName == null → HEAD_REF_NULL
  • 공백 → HEAD_REF_EMPTY

4.3 Repository 계층 예외 처리

Repository 는 파일 시스템과 도메인 사이의 경계이므로, IO 오류와 포맷 오류를 명확히 구분해 처리한다.

Index 관련 (FileIndexRepository)

  • 읽기 실패 → INDEX_FILE_READ_FAILED
  • 쓰기 실패 → INDEX_FILE_WRITE_FAILED

Object 관련 (FileObjectWriter, FileObjectReader)

  • 객체 디렉터리 생성 실패 → OBJECT_DIRECTORY_CREATE_FAILED
  • 객체 파일 쓰기 실패 → OBJECT_FILE_WRITE_FAILED
  • 객체 파일이 없으면 → OBJECT_FILE_NOT_FOUND
  • 읽기 실패 → OBJECT_FILE_READ_FAILED
  • Tree/Commit 포맷이 잘못된 경우 → MALFORMED_TREE_OBJECT, MALFORMED_COMMIT_OBJECT

Ref 관련 (FileRefRepository)

  • HEAD 또는 refs 읽기/쓰기 중 IO 오류 → FILE_IO_ERROR
  • HEAD 내용이 비어 있거나 포맷이 잘못된 경우 → HEAD_REF_EMPTY 또는 HEAD_REF_NULL

4.4 서비스 계층 예외 처리

Service 는 비즈니스 규칙 위반이나 사용자 입력 오류에 대해 명시적 예외를 던진다.

AddService

  • 경로 리스트가 비어 있으면 → EMPTY_PATHS
  • 파일 읽기 실패 → FILE_READ_FAILED

CommitService

  • 메시지/작성자 검증 실패 → COMMIT_MESSAGE_EMPTY, COMMIT_AUTHOR_EMPTY
  • Index 에 스테이징된 파일이 없으면 → “Nothing to commit” (명시적 메시지)

FileSystemInitService

  • 디렉터리/파일 생성 실패 → REPOSITORY_INIT_FAILED

StatusService

  • 워킹 트리 파일 읽기 실패 → FILE_READ_FAILED
  • 디렉터리 트리 순회 실패 → FILE_IO_ERROR
  • SHA-1 알고리즘 사용 불가 → SHA1_NOT_AVAILABLE

Remote 관련 Service

  • 파일 시스템, HTTP 통신 과정에서 IO 예외가 발생하면 적절한 ErrorCode 와 함께 IllegalArgumentException 으로 감싼다.
  • 비 Fast-Forward push/pull 의 경우는 예외가 아니라 결과 enum(NOT_FAST_FORWARD, REMOTE_REJECTED_NON_FF) 으로 표현한다.
전체 글 보기