우아한 테크코스 8기 오픈미션 보고서
이 글의 목차46개
- 1.1 프로젝트 목표
- 1.2 지원 명령어 및 기능 요약
- 기본 저장소 관리
- git init
- git add …
- git commit
- 상태 조회 및 히스토리
- git status
- git log
- 브랜치 및 브랜치 전환/병합
- git branch
- git checkout
- git merge (Fast-Forward only)
- 로컬 디렉터리 기반 Remote 연동
- git push / git pull / git clone
- 2.1 전체 아키텍처
- Application 계층
- Controller / View 계층
- Service 계층
- Repository / Remote 계층
- Domain 계층
- 2.2 Git 도메인 모델 핵심
- Blob
- Tree
- Commit
- Index
- Head
- 2.3 .javaGit 저장 구조와 Repository 요약
- 디렉터리/파일 구조
- Repository 역할 요약
- 2.4 명령어 흐름 개요 (입력 → 도메인/저장소)
- 입력 처리
- 주요 명령 흐름 요약
- 3.1 git init – 저장소 초기화
- 3.2 git add – 파일 스테이징
- 3.3 git commit – 스냅샷 확정
- 3.4 git status – 상태 조회
- 3.5 git log – 커밋 로그 조회
- 3.6 git branch / git checkout – 브랜치 관리와 전환
- 3.7 git merge – Fast-Forward 병합
- 3.8 git push / git pull / git clone – 로컬 디렉터리 기반 Remote
- 3.9 git serve-http / git push-http / git pull-http – HTTP Remote
- 4.1 ErrorCode 기반 표준 에러 포맷
- 4.2 도메인 계층 검증
- 4.3 Repository 계층 예외 처리
- 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 로 변환한다.
Head
- 현재 체크아웃된 브랜치 참조(예: 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)
주요 처리
- 인자 검증
- filePaths == null 또는 비어 있으면 ErrorCode.EMPTY_PATHS 로 예외를 던진다.
- 현재 Index 읽기
- IndexRepository.read() 로 기존 스테이징 상태를 읽고, LinkedHashMap 으로 복사한다.
- 각 파일 처리
- rootDirectoryPath.resolve(filePath) 로 절대 경로를 만든다.
- 파일이 존재하지 않거나 디렉터리이면 스킵한다.
- Files.readAllBytes 로 내용 읽기 → ObjectWriter.write(byte[]) 호출
- FileObjectWriter 가 SHA-1 해시 계산 후 .javaGit/objects 에 저장하고, 해시를 반환한다.
- Index Map 에 filePath → objectHash 로 업데이트한다.
- Index 쓰기
- 새 Index 도메인 객체를 생성하고 IndexRepository.write(index) 로 .javaGit/index 를 갱신한다.
3.3 git commit – 스냅샷 확정
목적: Index 에 있는 스테이징 내용을 기반으로 Tree/Commit 객체를 만들고, 현재 브랜치 HEAD 를 새 커밋으로 이동시킨다.
흐름
- GitController → CommitCmd.execute(args) → CommitService.commit(message, author)
주요 처리
- 메시지/작성자 검증
- message == null || isBlank → COMMIT_MESSAGE_EMPTY
- author == null || isBlank → COMMIT_AUTHOR_EMPTY
- Index 읽기 및 비어 있음 체크
- IndexRepository.read() 로 Index 획득
- index.stagedFiles() 가 비어 있으면 “Nothing to commit” 예외 던짐
- Tree 생성 및 저장
- new Tree(index.stagedFiles()) 로 Tree 도메인 객체 생성
- buildTreeContent(tree) 로 “blob
\n” 포맷 문자열 생성 - ObjectWriter.write(treeContentBytes) 로 Tree 객체 저장 → treeHash 반환
- 현재 HEAD 커밋 읽기
- RefRepository.readCurrentBranch() 로 현재 브랜치 이름
- RefRepository.readBranchHead(branch) 로 HEAD 커밋 SHA
- Commit 생성 및 저장
- new Commit(message, treeHash, parentHashOrNull, author) 생성
- buildCommitContent(commit) 으로 commit 텍스트 생성
- ObjectWriter.write(commitBytes) 로 commit 저장 → commitHash 반환
- HEAD 업데이트
- RefRepository.updateBranchHead(branch, commitHash) 로 현재 브랜치 HEAD 를 새 커밋으로 갱신한다.
3.4 git status – 상태 조회
목적: 워킹 트리, Index, HEAD 커밋 Tree 를 비교해, Git 과 유사한 상태 정보를 제공한다.
흐름
- GitController → StatusCmd.execute(args) → StatusService.status() → StatusResult
내부 동작
- 워킹 트리 스캔
- Files.walkFileTree(rootDirectoryPath) 로 모든 파일 탐색
- .javaGit 디렉터리는 SKIP_SUBTREE 로 건너뛴다.
- 각 regular file 에 대해:
- 상대 경로 path 계산 (\ → /)
- 파일 내용 SHA-1 해시 계산 → Map<String, String> working 에 path → sha 저장
- Index 및 HEAD Tree 스냅샷 읽기
- index = IndexRepository.read().stagedFiles()
- headTree = readHeadTreeSnapshot() (HEAD 커밋이 없으면 빈 Map)
- 스테이징 영역 비교 (HEAD vs Index)
- stagedAdded: Index 에 있고 HEAD 에 없는 경로
- stagedModified: 둘 다 있지만 SHA 가 다른 경로
- stagedDeleted: HEAD 에만 있는 경로
- 워킹 트리 vs Index 비교
- modifiedNotStaged: 둘 다 있지만 SHA 가 다른 경로
- deletedNotStaged: Index 에만 있고 워킹 트리에 없는 경로
- Untracked 계산
- 워킹 트리에 있지만 Index 에 없는 경로를 untracked 로 분류
- 결과 반환
- 위 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) 으로 표현한다.