Files
java-examples/docs/jgit.md

14 KiB

자바의 JGit 라이브러리에 대해 설명하자면, JGit은 Git 버전 관리 시스템을 자바 애플리케이션에서 사용할 수 있도록 해주는 순수 자바로 작성된 오픈소스 라이브러리입니다. 이 라이브러리는 Eclipse 재단에서 개발되었으며, Git의 기능을 프로그래밍 방식으로 활용할 수 있게 설계되었습니다. JGit을 사용하면 별도의 Git 클라이언트 설치 없이 자바 코드로 Git 저장소를 생성, 조작, 관리할 수 있습니다.

주요 특징

  1. 순수 자바 구현: JGit은 네이티브 Git 명령어에 의존하지 않고 자바로 작성되었기 때문에 플랫폼 독립적입니다. Windows, Linux, macOS 등 어디서나 동일하게 동작합니다.
  2. Git 기능 지원: JGit은 Git의 주요 기능을 대부분 지원합니다. 예를 들어, 저장소 초기화, 커밋, 브랜치 관리, 머지, 푸시/풀, 로그 조회 등이 가능합니다.
  3. 임베디드 사용: 외부 Git 설치 없이 애플리케이션에 임베드하여 사용할 수 있어, Git 기반 도구나 워크플로우를 자바로 쉽게 통합할 수 있습니다.
  4. Eclipse와의 통합: JGit은 Eclipse IDE의 Git 플러그인(EGit)의 핵심 구성 요소로 사용되며, IDE 내에서 Git 작업을 지원합니다.

주요 기능 예시

JGit을 사용하면 다음과 같은 작업을 자바 코드로 수행할 수 있습니다:

  • 저장소 초기화:
    Git.init().setDirectory(new File("/path/to/repo")).call();
    
  • 파일 추가 및 커밋:
    Git git = Git.open(new File("/path/to/repo/.git"));
    git.add().addFilepattern("file.txt").call();
    git.commit().setMessage("Initial commit").call();
    
  • 브랜치 생성:
    git.branchCreate().setName("feature-branch").call();
    
  • 원격 저장소에서 풀:
    git.pull().call();
    

사용 사례

  • 자동화 도구: CI/CD 파이프라인에서 Git 작업을 스크립트로 자동화.
  • 커스텀 Git 클라이언트: GUI나 CLI 기반의 자체 Git 도구 개발.
  • 프로젝트 관리: 소스 코드 관리나 버전 관리 기능을 애플리케이션에 통합.

장단점

장점

  • 설치 필요 없이 자바 환경에서 바로 사용 가능.
  • Git 명령어를 호출하는 대신 API로 세밀한 제어 가능.
  • 크로스 플랫폼 호환성.

단점

  • 네이티브 Git에 비해 일부 고급 기능이 제한적이거나 느릴 수 있음.
  • 대규모 저장소에서 성능 최적화가 필요할 수 있음.

의존성 추가 (Maven 예시)

JGit을 프로젝트에 추가하려면 Maven을 사용하는 경우 pom.xml에 다음 의존성을 추가하면 됩니다:

<dependency>
    <groupId>org.eclipse.jgit</groupId>
    <artifactId>org.eclipse.jgit</artifactId>
    <version>6.8.0.202311291450-r</version> <!-- 최신 버전 확인 필요 -->
</dependency>

참고 자료

추가로 궁금한 점이 있거나 특정 기능에 대해 더 알고 싶다면 말씀해주세요!


서버의 컨트롤러에서 Git 저장소를 제공한다는 것은 웹 애플리케이션의 백엔드에서 Git 저장소를 생성하고, 클라이언트가 이를 HTTP(S)를 통해 접근하거나 다운로드할 수 있도록 하는 것을 의미합니다. 여기서는 Spring Boot 기반 서버에서 JGit을 활용해 Git 저장소를 제공하는 방법을 단계별로 설명하겠습니다. 클라이언트가 저장소를 클론(clone)하거나 파일을 다운로드할 수 있는 시나리오를 중심으로 구성합니다.


목표

  • 서버에서 Git 저장소를 동적으로 생성하거나 관리.
  • HTTP 엔드포인트를 통해 저장소에 접근하거나 .git 디렉토리를 제공.
  • JGit을 사용해 저장소 초기화 및 기본 파일 추가.

1. 프로젝트 설정

Spring Boot와 JGit을 사용해 기본 환경을 설정합니다.

(1) 의존성 추가 (Maven)

pom.xml에 필요한 의존성을 추가:

<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Thymeleaf (선택적, UI용) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- JGit -->
    <dependency>
        <groupId>org.eclipse.jgit</groupId>
        <artifactId>org.eclipse.jgit</artifactId>
        <version>6.8.0.202311291450-r</version>
    </dependency>
    <!-- Lombok (선택적) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

(2) 프로젝트 구조

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── gitserver/
│   │               ├── controller/  # 컨트롤러
│   │               ├── service/     # Git 로직
│   │               └── GitServerApplication.java
│   └── resources/
│       └── application.properties  # 설정

2. Git 서비스 구현

GitService 클래스에서 저장소를 생성하고 관리하는 로직을 작성합니다.

package com.example.gitserver.service;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

@Service
public class GitService {

    // 저장소 생성 및 초기화
    public File createRepository(String repoName) throws GitAPIException, IOException {
        File repoDir = new File("./repos/" + repoName); // 서버 내 저장소 디렉토리
        if (!repoDir.exists()) {
            repoDir.mkdirs();
        }

        try (Git git = Git.init().setDirectory(repoDir).call()) {
            // 초기 파일 추가 (선택적)
            File readme = new File(repoDir, "README.md");
            Files.write(readme.toPath(), "# Sample Repository".getBytes());
            git.add().addFilepattern("README.md").call();
            git.commit().setMessage("Initial commit").call();
        }
        return repoDir;
    }

    // 저장소 디렉토리 반환
    public File getRepository(String repoName) {
        return new File("./repos/" + repoName);
    }
}

3. 컨트롤러에서 Git 저장소 제공

GitController에서 HTTP 엔드포인트를 통해 저장소를 제공합니다. 두 가지 방식으로 접근할 수 있습니다:

  1. 저장소 전체 다운로드: .git 디렉토리를 ZIP 파일로 제공.
  2. Git 프로토콜 지원: HTTP를 통해 git clone 가능하도록 설정 (추가 설정 필요).

(1) 저장소 생성 및 ZIP 다운로드 방식

package com.example.gitserver.controller;

import com.example.gitserver.service.GitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
@RequestMapping("/git")
public class GitController {

    @Autowired
    private GitService gitService;

    // 저장소 생성
    @PostMapping("/create/{repoName}")
    public ResponseEntity<String> createRepository(@PathVariable String repoName) {
        try {
            gitService.createRepository(repoName);
            return ResponseEntity.ok("Repository '" + repoName + "' created successfully");
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Error: " + e.getMessage());
        }
    }

    // 저장소 다운로드 (ZIP 형식)
    @GetMapping("/download/{repoName}")
    public ResponseEntity<Resource> downloadRepository(@PathVariable String repoName) throws Exception {
        File repoDir = gitService.getRepository(repoName);
        if (!repoDir.exists()) {
            return ResponseEntity.notFound().build();
        }

        // .git 디렉토리만 압축
        File gitDir = new File(repoDir, ".git");
        Path zipPath = Files.createTempFile(repoName, ".zip");
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) {
            zipDirectory(gitDir, gitDir.getName(), zos);
        }

        Resource resource = new FileSystemResource(zipPath.toFile());
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + repoName + ".zip")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(resource);
    }

    // 디렉토리를 ZIP으로 압축하는 헬퍼 메서드
    private void zipDirectory(File folder, String parentFolder, ZipOutputStream zos) throws IOException {
        for (File file : folder.listFiles()) {
            if (file.isDirectory()) {
                zipDirectory(file, parentFolder + "/" + file.getName(), zos);
                continue;
            }
            ZipEntry ze = new ZipEntry(parentFolder + "/" + file.getName());
            zos.putNextEntry(ze);
            Files.copy(file.toPath(), zos);
            zos.closeEntry();
        }
    }
}

(2) 실행 및 테스트

  • 애플리케이션 실행 후:
    • POST /git/create/myrepo: 저장소 생성.
    • GET /git/download/myrepo: .git 디렉토리를 ZIP으로 다운로드.
  • 클라이언트는 다운로드한 ZIP을 풀고 git clone --bare로 로컬 저장소로 변환 가능.

4. HTTP를 통한 Git 프로토콜 제공 (고급)

ZIP 다운로드 대신 클라이언트가 git clone https://your-server/git/myrepo로 직접 클론하도록 하려면 Git HTTP 백엔드를 구현해야 합니다. JGit은 기본적으로 Git 서버 프로토콜을 제공하지 않으므로 추가 설정이 필요합니다.

(1) JGit Servlet 사용

JGit은 GitServlet을 제공해 HTTP 기반 Git 프로토콜을 지원합니다.

  • 의존성 추가: JGit HTTP 서버 의존성 추가.

    <dependency>
        <groupId>org.eclipse.jgit</groupId>
        <artifactId>org.eclipse.jgit.http.server</artifactId>
        <version>6.8.0.202311291450-r</version>
    </dependency>
    
  • Servlet 설정:

    package com.example.gitserver.config;
    
    import org.eclipse.jgit.http.server.GitServlet;
    import org.springframework.boot.web.servlet.ServletRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.Servlet;
    import java.io.File;
    
    @Configuration
    public class GitServletConfig {
    
        @Bean
        public ServletRegistrationBean<Servlet> gitServlet() {
            GitServlet gitServlet = new GitServlet();
            gitServlet.setRepositoryResolver((req, name) -> {
                File repoDir = new File("./repos/" + name + "/.git");
                if (!repoDir.exists()) {
                    throw new IllegalStateException("Repository not found: " + name);
                }
                return new org.eclipse.jgit.internal.storage.file.FileRepository(repoDir);
            });
    
            ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(gitServlet, "/git/*");
            bean.setLoadOnStartup(1);
            return bean;
        }
    }
    
  • 저장소 준비: 저장소는 bare repository 형식이어야 합니다.

    public File createBareRepository(String repoName) throws GitAPIException {
        File repoDir = new File("./repos/" + repoName);
        if (!repoDir.exists()) {
            repoDir.mkdirs();
        }
        try (Git git = Git.init().setDirectory(repoDir).setBare(true).call()) {
            System.out.println("Bare repository created: " + repoName);
        }
        return repoDir;
    }
    
  • 컨트롤러 수정:

    @PostMapping("/create-bare/{repoName}")
    public ResponseEntity<String> createBareRepository(@PathVariable String repoName) {
        try {
            gitService.createBareRepository(repoName);
            return ResponseEntity.ok("Bare repository '" + repoName + "' created successfully");
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Error: " + e.getMessage());
        }
    }
    

(2) 클라이언트에서 클론

  • 서버가 http://localhost:8080에서 실행 중이라면:
    git clone http://localhost:8080/git/myrepo
    

5. 추가 고려사항

  • 보안: 인증(예: Spring Security)을 추가해 저장소 접근을 제어.
  • 저장소 경로: ./repos/는 서버의 파일 시스템에 저장되므로, 실제 배포 시 경로를 환경 변수로 관리.
  • 성능: 대규모 저장소의 경우 JGit 대신 네이티브 Git 서버(GitLab, Gitea 등)를 고려.

결론

  • ZIP 방식: 간단히 .git 디렉토리를 제공하며, 클라이언트가 수동으로 처리.
  • Git 프로토콜 방식: GitServlet을 사용해 HTTP로 직접 클론 가능하지만 설정이 복잡. 필요에 따라 두 방식 중 하나를 선택하거나 혼합해 사용할 수 있습니다. 추가 질문이나 특정 기능 확장이 필요하면 말씀해주세요!