728x90
들어가며
이 글에서는 GitHub 클론 프로젝트에서 구현한 커밋 등록, 브랜치 병합 (Merge), 리베이스 (Rebase) 기능의 핵심 구조와 처리 흐름을 정리합니다.
DAG 기반의 브랜치-커밋 구조를 바탕으로, Git 의 동작을 단순화해 웹 애플리케이션에서 표현했습니다.
커밋 도메인 구조
- 커밋 테이블과 브랜치 테이블 간 관계
- `tb_branch` : `tb_commit` = 1:N
- 브랜치는 커밋이 없을 수도 있고, 하나 이상의 커밋을 가질 수 있다
- `tb_branch` : `tb_commit` = 1:N
- 커밋 도메인 주요 컬럼 설명
- `BRANCH_SEQ` : 해당 커밋이 속한 브랜치의 ID
- `PARENT_COMMIT_SEQ` : 이전 커밋의 ID. 일반적인 커밋 흐름 연결을 위한 필드
- `MERGE_FROM_COMMIT_SEQ` : 병합(Merge) 시 사용된 다른 부모 커밋 ID. merge 커밋일 경우 사용
- `REBASE_ORIGIN_SEQ` : rebase된 커밋이 원래 참조하던 커밋의 ID. 기록 보존용
구조적 특징 요약
- DAG 구조 기반 설계
- 일반 커밋은 `PARENT_COMMIT_SEQ` 를 통해 직전 커밋과 연결
- Merge 커밋은 `MERGE_FROM_COMMIT_SEQ` 를 추가로 설정하여 2개의 부모 커밋을 가짐
- Rebase 처리된 커밋은 `REBASE_ORIGIN_SEQ` 를 통해 원래 위치 정보를 별도로 기록함
- 시간 정렬 가능
- `COMMIT_YMD`, `COMMIT_HM` 을 통해 커밋 시각 순서 파악이 용이하며 시각화 그래프 처리 시 사용됨
- 논리 삭제 지원
- 커밋 데이터는 직접 삭제하지 않고 `DEL_YN = 'Y'` 로 처리하여 삭제된 것처럼 보이게 처리 가능
커밋 종류
- 일반 커밋: `PARENT_COMMIT_SEQ` 만 설정됨
- Merge 커밋: `PARENT_COMMIT_SEQ` + `MERGE_FROM_COMMIT_SEQ`
- Rebase 커밋: `PARENT_COMMIT_SEQ` 는 새 기준 브랜치를 가리키고, `REBASE_ORIGIN_SEQ` 는 원래의 커밋 부모를 가리킴 (기록용)
커밋 기능
src
└── main
├── java
│ └── com.example
│ ├── core
│ └── javastudy
│ └── commit
│ ├──model
│ ├──controller
│ └──service
...
- `com.example.javastudy` 하위에 `commit` 패키지 생성
- 커밋 도메인과 관련된 기능을 관리
- 기능별로 응집된 구조를 유지할 수 있도록 설계
- 하위에 `model`, `controller`, `service` 패키지 및 각 `dto`, `controller`, `service`, `mapper` 클래스 생성
// Service
public boolean insertCommit(CommitDto dto) {
dto.generateSeq();
setCommitInfo(dto);
commitMapper.insertCommit(dto.toEntity());
updateLastCommitInfo(dto);
return true;
}
private void setCommitInfo(CommitDto dto) {
dto.setCommitterId(SecurityUtils.Impl.getMemberIdInPrinciple());
dto.setCommitYmd(DateUtils.getDate(2));
dto.setCommitHm(DateUtils.getDate(11));
if (StringUtils.isBlank(dto.getParentCommitSeq())) {
BranchDto branch = branchService.selectBranch(BranchDto.builder().seq(dto.getBranchSeq()).build());
dto.setParentCommitSeq(branch.getLastCommitSeq());
}
}
private void updateLastCommitInfo(CommitDto dto) {
BranchDto branch = setBranchLastCommitInfo(dto, true);
branchService.updateBranch(branch);
ProjectDto projectDto = setProjectLastCommitInfo(branchService.selectBranch(branch));
projectService.updateProject(projectDto);
}
private BranchDto setBranchLastCommitInfo(CommitDto commit, boolean updateCommitCount) {
return BranchDto.builder()
.seq(commit.getBranchSeq())
.lastCommitSeq(commit.getSeq())
.lastCommitMessage(commit.getCommitMessage())
.lastCommitYmd(commit.getCommitYmd())
.lastCommitHm(commit.getCommitHm())
.commitCount(updateCommitCount ? "Y" : null)
.build();
}
private ProjectDto setProjectLastCommitInfo(BranchDto branch) {
ProjectDto projectDto = ProjectDto.builder()
.seq(branch.getProjectSeq())
.lastCommitMessage(branch.getLastCommitMessage())
.lastCommitYmd(branch.getLastCommitYmd())
.lastCommitHm(branch.getLastCommitHm())
.build();
if ("Y".equals(branch.getIsDefault())) {
projectDto.setCommitCount(branch.getCommitCount());
}
return projectDto;
}
- Controller 단 : `POST /commit/add` 요청을 통해 그룹 생성 처리
- Service 단 : 외부 요청/응답용 `CommitDto` 를 내부 도메인 객체로 변환
- `ULID` 를 이용해 PK 값인 `SEQ` 생성
- 현재 로그인한 사용자의 `memberId` 를 `CommiterId` 필드에 저장
- 커밋한 일자/시간 저장
- 부모 커밋 SEQ 가 없을 경우 해당 브랜치에 저장된 마지막 커밋 SEQ 조회하여 저장
- 커밋 시 해당 브랜치와 프로젝트의 `lastCommitSeq` 등 마지막 커밋 정보 갱신
- Mapper 단 : 내부 도메인 객체를 DB 에 저장
CRUD 전체 코드는 아래 GitHub 링크에서 확인하실 수 있습니다.
commit.service.CommitService.java
Merge 기능
// Service
public boolean insertMergeCommit(BranchDto branchDto) {
CommitDto commitDto = CommitDto.builder()
.branchSeq(branchDto.getFromBranchSeq())
.parentCommitSeq(branchDto.getLastCommitSeq())
.commitMessage("Merge branch '" + branchDto.getFromBranchName() + "' into " + branchDto.getBranchName())
.mergeFromCommitSeq(branchDto.getFromBranchLastCommitSeq())
.build();
insertCommit(commitDto);
return true;
}
- 기준 브랜치에 다른 브랜치의 마지막 커밋을 병합
- 병합된 커밋은 두 개의 부모 커밋 ID (`parentCommitSeq`, `mergeFromCommitSeq`) 를 가짐
→ DAG 구조상 병합 노드 처리 - 병합 후 기준 브랜치의 `lastCommitSeq` 갱신
Rebase 기능
// Service
public boolean rebaseBranch(BranchDto originBranch) {
AtomicReference<String> lastCommitSeq = new AtomicReference<>("");
CommitDto commitDto = CommitDto.builder()
.toBranchLastCommitSeq(originBranch.getLastCommitSeq())
.fromBranchLastCommitSeq(originBranch.getFromBranchLastCommitSeq())
.build();
List<CommitDto> list = selectRebaseCommitList(commitDto);
list.forEach(dto -> {
dto.setSeq(Ulid.createUlid());
if (StringUtils.isBlank(dto.getParentCommitSeq())) {
dto.setParentCommitSeq(originBranch.getFromBranchLastCommitSeq());
}
rebaseCommit(dto); // rebase 한 복사본 커밋 추가
deleteCommit(dto); // 기존 커밋 숨김 처리
lastCommitSeq.set(dto.getSeq());
});
CommitDto lastCommit = selectCommit(lastCommitSeq);
BranchDto updateBranch = setBranchLastCommitInfo(lastCommit, false);
branchService.updateBranch(updateBranch);
return true;
}
- 대상 브랜치의 커밋들을 새로운 기준 브랜치 위로 재배치
- 실제 커밋 복제는 하지 않고, 부모 커밋 관계(`parentCommitSeq`) 를 변경하여 DAG 재구성
- 시각적으로는 커밋 위치가 바뀌는 것처럼 표현
- 원본 커밋을 삭제하지 않기 때문에, Git의 실제 rebase와는 차이가 있음 (안전한 단순화 구현)
다음 글에서는 DAG 기반 커밋 정보를 실제 그래프 형태로 시각화(Mermaid.js / GitGraph.js) 하는 과정을 다룰 예정입니다.
관련 커밋
728x90
'Java | Spring > GitHub Clone Project' 카테고리의 다른 글
[GitHub Clone] 06. 커밋 그래프 시각화 (Mermaid.js vs GitGraph.js) (1) | 2025.04.18 |
---|---|
[GitHub Clone] 04. 그룹 / 프로젝트 / 브랜치 도메인 설계 및 CRUD 구현 (1) | 2025.04.14 |
[GitHub Clone] 03. 회원 기능 및 로그인 (Session → Spring Security) (1) | 2025.04.11 |
[GitHub Clone] 02. 프로젝트 구조 및 초기 설정 (2) | 2025.04.09 |
[GitHub Clone] 01. 프로젝트 소개 및 기술 스택 정리 (0) | 2025.04.07 |