CtrlK
BlogDocsLog inGet started
Tessl Logo

finkel/jgit

JGit documentation and API reference with code examples

92

1.09x
Quality

Pending

Does it follow best practices?

Impact

92%

1.09x

Average score across 10 eval scenarios

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

index.mddocs/patterns/

Common Patterns and Best Practices

This section covers common patterns, best practices, and real-world usage scenarios for JGit.

Table of Contents

  1. Repository Management
  2. Error Handling
  3. Performance Optimization
  4. Thread Safety
  5. Memory Management
  6. Integration Patterns
  7. Testing

Repository Management

Repository Lifecycle Pattern

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import java.io.File;
import java.io.IOException;

public class RepositoryLifecycle {

    /**
     * Safe repository access pattern using try-with-resources
     */
    public static void withRepository(String repoPath, RepositoryOperation operation)
            throws IOException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();

        try (Repository repository = builder
                .setGitDir(new File(repoPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .setMustExist(true)
                .build()) {

            operation.execute(repository);
        }
    }

    /**
     * Repository operation interface
     */
    @FunctionalInterface
    public interface RepositoryOperation {
        void execute(Repository repository) throws Exception;
    }

    /**
     * Example usage
     */
    public static void main(String[] args) {
        try {
            withRepository("/path/to/repo", repo -> {
                System.out.println("Repository: " + repo.getDirectory());
                System.out.println("Work tree: " + repo.getWorkTree());
                System.out.println("Is bare: " + repo.isBare());

                // Perform repository operations
                // ...
            });
        } catch (IOException e) {
            System.err.println("Failed to access repository: " + e.getMessage());
        }
    }
}

Repository Factory Pattern

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import java.io.File;
import java.io.IOException;
import java.util.function.Supplier;

public class RepositoryFactory {

    private final String basePath;

    public RepositoryFactory(String basePath) {
        this.basePath = basePath;
    }

    /**
     * Create repository supplier for lazy initialization
     */
    public Supplier<Repository> createRepositorySupplier(String repoName) {
        return () -> {
            try {
                return new FileRepositoryBuilder()
                    .setGitDir(new File(basePath, repoName + "/.git"))
                    .setMustExist(true)
                    .build();
            } catch (IOException e) {
                throw new RuntimeException("Failed to open repository: " + repoName, e);
            }
        };
    }

    /**
     * Create repository with retry logic
     */
    public Repository createRepositoryWithRetry(String repoName, int maxRetries)
            throws IOException {
        IOException lastException = null;

        for (int i = 0; i < maxRetries; i++) {
            try {
                return new FileRepositoryBuilder()
                    .setGitDir(new File(basePath, repoName + "/.git"))
                    .build();
            } catch (IOException e) {
                lastException = e;
                if (i < maxRetries - 1) {
                    try {
                        Thread.sleep(100 * (i + 1)); // Exponential backoff
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new IOException("Interrupted during retry", ie);
                    }
                }
            }
        }

        throw lastException;
    }
}

Error Handling

Comprehensive Error Handling

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import java.io.File;
import java.io.IOException;

public class GitErrorHandling {

    public enum GitOperation {
        CLONE, PUSH, PULL, FETCH, COMMIT, MERGE
    }

    /**
     * Execute Git operation with comprehensive error handling
     */
    public static <T> T executeGitOperation(
            GitOperation operation,
            GitOperationExecutor<T> executor,
            ErrorHandler errorHandler) {

        try {
            return executor.execute();
        } catch (RepositoryNotFoundException e) {
            return errorHandler.handleRepositoryNotFound(operation, e);
        } catch (GitAPIException e) {
            return errorHandler.handleGitApiError(operation, e);
        } catch (IOException e) {
            return errorHandler.handleIoError(operation, e);
        } catch (Exception e) {
            return errorHandler.handleUnexpectedError(operation, e);
        }
    }

    @FunctionalInterface
    public interface GitOperationExecutor<T> {
        T execute() throws Exception;
    }

    public interface ErrorHandler<T> {
        T handleRepositoryNotFound(GitOperation operation, RepositoryNotFoundException e);
        T handleGitApiError(GitOperation operation, GitAPIException e);
        T handleIoError(GitOperation operation, IOException e);
        T handleUnexpectedError(GitOperation operation, Exception e);
    }

    /**
     * Example error handler implementation
     */
    public static class LoggingErrorHandler<T> implements ErrorHandler<T> {
        private final T defaultValue;

        public LoggingErrorHandler(T defaultValue) {
            this.defaultValue = defaultValue;
        }

        @Override
        public T handleRepositoryNotFound(GitOperation operation,
                                         RepositoryNotFoundException e) {
            System.err.println("Repository not found for " + operation + ": " + e.getMessage());
            return defaultValue;
        }

        @Override
        public T handleGitApiError(GitOperation operation, GitAPIException e) {
            System.err.println("Git API error during " + operation + ": " + e.getMessage());
            return defaultValue;
        }

        @Override
        public T handleIoError(GitOperation operation, IOException e) {
            System.err.println("IO error during " + operation + ": " + e.getMessage());
            return defaultValue;
        }

        @Override
        public T handleUnexpectedError(GitOperation operation, Exception e) {
            System.err.println("Unexpected error during " + operation + ": " + e.getMessage());
            e.printStackTrace();
            return defaultValue;
        }
    }
}

Retry Pattern for Network Operations

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class RetryPattern {

    /**
     * Execute operation with exponential backoff retry
     */
    public static <T> T executeWithRetry(
            Callable<T> operation,
            int maxRetries,
            long initialDelayMs,
            Class<? extends Exception>... retryableExceptions) throws Exception {

        Exception lastException = null;

        for (int attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                return operation.call();
            } catch (Exception e) {
                lastException = e;

                // Check if exception is retryable
                boolean shouldRetry = false;
                for (Class<? extends Exception> retryable : retryableExceptions) {
                    if (retryable.isInstance(e)) {
                        shouldRetry = true;
                        break;
                    }
                }

                if (!shouldRetry || attempt == maxRetries) {
                    break;
                }

                // Exponential backoff
                long delay = initialDelayMs * (1L << (attempt - 1));
                System.out.println("Attempt " + attempt + " failed, retrying in " +
                                 delay + "ms: " + e.getMessage());

                try {
                    TimeUnit.MILLISECONDS.sleep(delay);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new IOException("Interrupted during retry", ie);
                }
            }
        }

        throw lastException;
    }

    /**
     * Example: Fetch with retry for network issues
     */
    public static void fetchWithRetry(Repository repository) throws Exception {
        executeWithRetry(() -> {
            try (Git git = new Git(repository)) {
                git.fetch().call();
                return null;
            }
        }, 3, 1000, TransportException.class);
    }
}

Performance Optimization

Batch Operations

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class BatchOperations {

    /**
     * Batch commit multiple files
     */
    public static void commitMultipleFiles(
            Repository repository,
            List<String> filePaths,
            String commitMessage) throws IOException, GitAPIException {

        try (Git git = new Git(repository)) {
            // Add all files
            for (String filePath : filePaths) {
                git.add()
                    .addFilepattern(filePath)
                    .call();
            }

            // Single commit
            git.commit()
                .setMessage(commitMessage)
                .call();
        }
    }

    /**
     * Process commits in batches
     */
    public static void processCommitsInBatches(
            Repository repository,
            int batchSize,
            CommitProcessor processor) throws IOException {

        try (Git git = new Git(repository)) {
            var commits = git.log().all().call();
            List<Object> batch = new ArrayList<>();

            for (var commit : commits) {
                batch.add(commit);

                if (batch.size() >= batchSize) {
                    processor.process(batch);
                    batch.clear();
                }
            }

            // Process remaining commits
            if (!batch.isEmpty()) {
                processor.process(batch);
            }
        }
    }

    @FunctionalInterface
    public interface CommitProcessor {
        void process(List<Object> commits) throws IOException;
    }
}

Caching Pattern

import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.concurrent.*;

public class GitCache {

    private final Repository repository;
    private final Cache<ObjectId, byte[]> blobCache;
    private final Cache<String, ObjectId> refCache;

    public GitCache(Repository repository, int maxSize) {
        this.repository = repository;
        this.blobCache = new ConcurrentCache<>(maxSize);
        this.refCache = new ConcurrentCache<>(maxSize);
    }

    /**
     * Get blob content with caching
     */
    public byte[] getBlobContent(ObjectId blobId) throws IOException {
        byte[] content = blobCache.get(blobId);
        if (content == null) {
            content = repository.open(blobId).getBytes();
            blobCache.put(blobId, content);
        }
        return content;
    }

    /**
     * Resolve reference with caching
     */
    public ObjectId resolveRef(String refName) throws IOException {
        ObjectId objectId = refCache.get(refName);
        if (objectId == null) {
            objectId = repository.resolve(refName);
            if (objectId != null) {
                refCache.put(refName, objectId);
            }
        }
        return objectId;
    }

    /**
     * Clear cache
     */
    public void clear() {
        blobCache.clear();
        refCache.clear();
    }

    /**
     * Simple cache interface
     */
    private interface Cache<K, V> {
        V get(K key);
        void put(K key, V value);
        void clear();
    }

    /**
     * Concurrent cache implementation using LinkedHashMap
     */
    private static class ConcurrentCache<K, V> implements Cache<K, V> {
        private final ConcurrentHashMap<K, V> cache;
        private final int maxSize;

        public ConcurrentCache(int maxSize) {
            this.maxSize = maxSize;
            this.cache = new ConcurrentHashMap<>();
        }

        @Override
        public V get(K key) {
            return cache.get(key);
        }

        @Override
        public void put(K key, V value) {
            if (cache.size() >= maxSize) {
                // Simple eviction: remove first entry (FIFO)
                if (!cache.isEmpty()) {
                    cache.remove(cache.keys().nextElement());
                }
            }
            cache.put(key, value);
        }

        @Override
        public void clear() {
            cache.clear();
        }
    }
}

Thread Safety

Thread-Safe Repository Access

import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ThreadSafeRepository {

    private final Repository repository;
    private final ReadWriteLock lock;

    public ThreadSafeRepository(Repository repository) {
        this.repository = repository;
        this.lock = new ReentrantReadWriteLock();
    }

    /**
     * Execute read operation with shared lock
     */
    public <T> T read(RepositoryReadOperation<T> operation) throws IOException {
        lock.readLock().lock();
        try {
            return operation.execute(repository);
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * Execute write operation with exclusive lock
     */
    public <T> T write(RepositoryWriteOperation<T> operation) throws IOException {
        lock.writeLock().lock();
        try {
            return operation.execute(repository);
        } finally {
            lock.writeLock().unlock();
        }
    }

    @FunctionalInterface
    public interface RepositoryReadOperation<T> {
        T execute(Repository repository) throws IOException;
    }

    @FunctionalInterface
    public interface RepositoryWriteOperation<T> {
        T execute(Repository repository) throws IOException;
    }

    /**
     * Close repository safely
     */
    public void close() {
        lock.writeLock().lock();
        try {
            repository.close();
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Concurrent Operations Manager

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.concurrent.*;

public class ConcurrentGitOperations {

    private final ExecutorService executor;
    private final Repository repository;

    public ConcurrentGitOperations(Repository repository, int threadPoolSize) {
        this.repository = repository;
        this.executor = Executors.newFixedThreadPool(threadPoolSize);
    }

    /**
     * Execute multiple Git operations concurrently
     */
    public List<Future<?>> executeConcurrently(List<Runnable> operations) {
        List<Future<?>> futures = new ArrayList<>();

        for (Runnable operation : operations) {
            futures.add(executor.submit(() -> {
                try (Git git = new Git(repository)) {
                    operation.run();
                } catch (Exception e) {
                    System.err.println("Error in concurrent operation: " + e.getMessage());
                    throw new RuntimeException(e);
                }
            }));
        }

        return futures;
    }

    /**
     * Wait for all operations to complete
     */
    public void awaitCompletion(List<Future<?>> futures, long timeout, TimeUnit unit)
            throws InterruptedException, TimeoutException, ExecutionException {

        for (Future<?> future : futures) {
            future.get(timeout, unit);
        }
    }

    /**
     * Shutdown executor
     */
    public void shutdown() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

Memory Management

Resource Cleanup Pattern

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ResourceCleanup {

    /**
     * AutoCloseable wrapper for multiple resources
     */
    public static class GitResources implements AutoCloseable {
        private final List<AutoCloseable> resources;

        public GitResources() {
            this.resources = new ArrayList<>();
        }

        public <T extends AutoCloseable> T register(T resource) {
            resources.add(resource);
            return resource;
        }

        @Override
        public void close() {
            // Close in reverse order (LIFO)
            for (int i = resources.size() - 1; i >= 0; i--) {
                try {
                    resources.get(i).close();
                } catch (Exception e) {
                    System.err.println("Error closing resource: " + e.getMessage());
                }
            }
            resources.clear();
        }
    }

    /**
     * Safe resource usage example
     */
    public static void processRepositorySafely(Repository repository) throws IOException {
        try (GitResources resources = new GitResources()) {
            // Register all resources
            Git git = resources.register(new Git(repository));
            RevWalk walk = resources.register(new RevWalk(repository));
            TreeWalk treeWalk = resources.register(new TreeWalk(repository));

            // Use resources
            ObjectId headId = repository.resolve("HEAD");
            RevCommit head = walk.parseCommit(headId);
            treeWalk.addTree(head.getTree());

            while (treeWalk.next()) {
                // Process tree entries
                System.out.println(treeWalk.getPathString());
            }

            // Resources will be auto-closed in reverse order
        }
    }
}

Large Repository Handling

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import java.io.IOException;
import java.util.Iterator;

public class LargeRepositoryHandler {

    /**
     * Process large commit history in chunks
     */
    public static void processLargeCommitHistory(
            Repository repository,
            int chunkSize,
            CommitChunkProcessor processor) throws IOException, GitAPIException {

        try (Git git = new Git(repository)) {
            Iterator<RevCommit> commits = git.log().all().call().iterator();

            while (commits.hasNext()) {
                List<RevCommit> chunk = new ArrayList<>();

                // Collect chunk
                for (int i = 0; i < chunkSize && commits.hasNext(); i++) {
                    chunk.add(commits.next());
                }

                // Process chunk
                processor.process(chunk);

                // Hint to GC
                if (chunk.size() == chunkSize) {
                    System.gc();
                }
            }
        }
    }

    @FunctionalInterface
    public interface CommitChunkProcessor {
        void process(List<RevCommit> chunk) throws IOException;
    }

    /**
     * Memory-efficient file processing
     */
    public static void processFilesEfficiently(
            Repository repository,
            FileProcessor processor) throws IOException {

        // Use streaming where possible
        // Process files one at a time to avoid loading everything into memory

        try (Git git = new Git(repository)) {
            var commits = git.log().setMaxCount(1).call();
            var commit = commits.iterator().next();

            try (var treeWalk = new TreeWalk(repository)) {
                treeWalk.addTree(commit.getTree());
                treeWalk.setRecursive(true);

                while (treeWalk.next()) {
                    // Load file content only when needed
                    byte[] content = repository.open(treeWalk.getObjectId(0)).getBytes();
                    processor.process(treeWalk.getPathString(), content);

                    // Clear reference to allow GC
                    content = null;

                    // Periodically hint GC for large repositories
                    if (treeWalk.getTreeCount() % 1000 == 0) {
                        System.gc();
                    }
                }
            }
        }
    }

    @FunctionalInterface
    public interface FileProcessor {
        void process(String path, byte[] content) throws IOException;
    }
}

Integration Patterns

Integration with Build Systems

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import java.io.File;
import java.io.IOException;

public class BuildSystemIntegration {

    /**
     * Git information for build metadata
     */
    public static class BuildMetadata {
        private final String commitId;
        private final String branch;
        private final String tag;
        private final boolean dirty;
        private final String commitMessage;

        public BuildMetadata(String commitId, String branch, String tag,
                           boolean dirty, String commitMessage) {
            this.commitId = commitId;
            this.branch = branch;
            this.tag = tag;
            this.dirty = dirty;
            this.commitMessage = commitMessage;
        }

        // Getters and utility methods
        public String getVersionString() {
            return tag != null ? tag : commitId.substring(0, 8) + (dirty ? "-dirty" : "");
        }
    }

    /**
     * Extract build metadata from Git
     */
    public static BuildMetadata extractBuildMetadata(Repository repository)
            throws IOException, GitAPIException {

        try (Git git = new Git(repository)) {
            // Get current commit
            String commitId = repository.resolve("HEAD").getName();

            // Get branch
            String branch = repository.getBranch();

            // Check if working tree is dirty
            boolean dirty = !git.status().call().isClean();

            // Get commit message
            String commitMessage = git.log().setMaxCount(1).call()
                .iterator().next().getFullMessage();

            // Try to find tag
            String tag = findTagForCommit(repository, commitId);

            return new BuildMetadata(commitId, branch, tag, dirty, commitMessage);
        }
    }

    private static String findTagForCommit(Repository repository, String commitId)
            throws GitAPIException {

        try (Git git = new Git(repository)) {
            for (var ref : git.tagList().call()) {
                if (ref.getObjectId().getName().startsWith(commitId)) {
                    return ref.getName().replace("refs/tags/", "");
                }
            }
        }
        return null;
    }
}

CI/CD Integration

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import java.io.File;
import java.io.IOException;
import java.util.Properties;

public class CiCdIntegration {

    /**
     * Generate build properties from Git
     */
    public static Properties generateBuildProperties(Repository repository)
            throws IOException, GitAPIException {

        Properties props = new Properties();

        try (Git git = new Git(repository)) {
            // Basic Git info
            props.setProperty("git.commit.id", repository.resolve("HEAD").getName());
            props.setProperty("git.branch", repository.getBranch());

            // Commit info
            var commit = git.log().setMaxCount(1).call().iterator().next();
            props.setProperty("git.commit.message", commit.getShortMessage());
            props.setProperty("git.commit.user.name", commit.getAuthorIdent().getName());
            props.setProperty("git.commit.user.email", commit.getAuthorIdent().getEmailAddress());
            props.setProperty("git.commit.time", commit.getAuthorIdent().getWhen().toString());

            // Repository state
            boolean isClean = git.status().call().isClean();
            props.setProperty("git.dirty", String.valueOf(!isClean));

            // Remote URL if available
            var config = repository.getConfig();
            String remoteUrl = config.getString("remote", "origin", "url");
            if (remoteUrl != null) {
                props.setProperty("git.remote.origin.url", remoteUrl);
            }
        }

        return props;
    }

    /**
     * Create Git tag for release
     */
    public static void tagRelease(
            Repository repository,
            String version,
            String message,
            boolean annotated) throws GitAPIException {

        try (Git git = new Git(repository)) {
            if (annotated) {
                git.tag()
                    .setName(version)
                    .setMessage(message)
                    .call();
            } else {
                git.tag()
                    .setName(version)
                    .call();
            }

            // Push tag to remote
            git.push()
                .setPushTags()
                .call();
        }
    }
}

Testing

Test Utilities

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class GitTestUtils {

    /**
     * Create temporary test repository
     */
    public static TestRepository createTestRepository() throws IOException, GitAPIException {
        Path tempDir = Files.createTempDirectory("jgit-test-");
        File repoDir = tempDir.toFile();

        try (Git git = Git.init().setDirectory(repoDir).call()) {
            // Create initial commit
            File readme = new File(repoDir, "README.md");
            Files.writeString(readme.toPath(), "# Test Repository\n");

            git.add().addFilepattern("README.md").call();
            git.commit().setMessage("Initial commit").call();

            return new TestRepository(git.getRepository(), tempDir);
        }
    }

    public static class TestRepository implements AutoCloseable {
        private final Repository repository;
        private final Path tempDir;

        public TestRepository(Repository repository, Path tempDir) {
            this.repository = repository;
            this.tempDir = tempDir;
        }

        public Repository getRepository() {
            return repository;
        }

        public Path getTempDir() {
            return tempDir;
        }

        @Override
        public void close() throws Exception {
            repository.close();

            // Delete temporary directory
            deleteRecursively(tempDir.toFile());
        }

        private void deleteRecursively(File file) {
            if (file.isDirectory()) {
                for (File child : file.listFiles()) {
                    deleteRecursively(child);
                }
            }
            file.delete();
        }
    }

    /**
     * Create test repository with specific content
     */
    public static Repository createRepositoryWithContent(
            File repoDir,
            String... fileContents) throws IOException, GitAPIException {

        try (Git git = Git.init().setDirectory(repoDir).call()) {
            for (int i = 0; i < fileContents.length; i++) {
                File file = new File(repoDir, "file" + i + ".txt");
                Files.writeString(file.toPath(), fileContents[i]);

                git.add().addFilepattern("file" + i + ".txt").call();
                git.commit().setMessage("Add file " + i).call();
            }
            return git.getRepository();
        }
    }
}

Mock Repository for Testing

import org.eclipse.jgit.lib.Repository;
import java.io.IOException;

public class MockRepositoryExample {

    /**
     * Repository interface for testing
     */
    public interface TestableRepository {
        String getBranch() throws IOException;
        boolean isBare();
        // Add other methods needed for testing
    }

    /**
     * Real repository wrapper
     */
    public static class RealRepositoryWrapper implements TestableRepository {
        private final Repository repository;

        public RealRepositoryWrapper(Repository repository) {
            this.repository = repository;
        }

        @Override
        public String getBranch() throws IOException {
            return repository.getBranch();
        }

        @Override
        public boolean isBare() {
            return repository.isBare();
        }
    }

    /**
     * Mock repository for unit tests
     */
    public static class MockRepository implements TestableRepository {
        private final String branch;
        private final boolean bare;

        public MockRepository(String branch, boolean bare) {
            this.branch = branch;
            this.bare = bare;
        }

        @Override
        public String getBranch() {
            return branch;
        }

        @Override
        public boolean isBare() {
            return bare;
        }
    }
}

Memory Optimization with RevWalk

The RevWalk class is designed to be lightweight, but can still consume significant memory when walking large commit graphs. This section provides techniques to reduce memory usage when working with revision walks.

Restrict the Walked Revision Graph

Only walk the portion of the graph you actually need. Use markStart() for the starting point and markUninteresting() for commits you don't need to traverse.

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException;

public class RestrictedRevWalkExample {

    public static void walkRestrictedGraph(Repository repository) throws IOException {
        RevWalk walk = new RevWalk(repository);

        // Only walk commits in master not yet in origin/master
        ObjectId from = repository.resolve("refs/heads/master");
        ObjectId to = repository.resolve("refs/remotes/origin/master");

        walk.markStart(walk.parseCommit(from));
        walk.markUninteresting(walk.parseCommit(to));

        for (RevCommit commit : walk) {
            // Process commits between from and to
            System.out.println(commit.getShortMessage());
        }

        walk.dispose();
    }
}

Discard Commit Bodies When Not Needed

If you don't need author, committer, or message information, disable retaining commit bodies.

public class RevWalkWithoutBodies {

    public static void walkWithoutCommitBodies(Repository repository) throws IOException {
        RevWalk walk = new RevWalk(repository);
        walk.setRetainBody(false); // Discard commit bodies

        walk.markStart(walk.parseCommit(repository.resolve("HEAD")));

        for (RevCommit commit : walk) {
            // Can access commit ID but not message/author info
            System.out.println("Commit: " + commit.getId().getName());
            // commit.getFullMessage() would throw exception
        }

        walk.dispose();
    }
}

Extract Needed Data and Dispose

Extract only the data you need from commits, then call dispose() to release memory.

import java.util.HashSet;
import java.util.Set;

public class ExtractAndDisposeExample {

    public static Set<String> collectAuthorEmails(Repository repository) throws IOException {
        RevWalk walk = new RevWalk(repository);
        Set<String> authorEmails = new HashSet<>();

        walk.markStart(walk.parseCommit(repository.resolve("HEAD")));

        for (RevCommit commit : walk) {
            // Extract needed data
            authorEmails.add(commit.getAuthorIdent().getEmailAddress());
            commit.dispose(); // Release memory for this commit
        }

        walk.dispose();
        return authorEmails;
    }
}

Subclass RevWalk for Custom Data

When you need to attach additional data to commits, subclass both RevWalk and RevCommit instead of using external maps.

import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.lib.AnyObjectId;
import java.util.Date;

public class CustomRevWalkExample {

    public static class ReviewedRevision extends RevCommit {
        private final Date reviewDate;

        public ReviewedRevision(AnyObjectId id, Date reviewDate) {
            super(id);
            this.reviewDate = reviewDate;
        }

        public Date getReviewDate() {
            return reviewDate;
        }
    }

    public static class ReviewedWalk extends RevWalk {
        public ReviewedWalk(Repository repo) {
            super(repo);
        }

        @Override
        protected RevCommit createCommit(AnyObjectId id) {
            // Create custom commit with review date
            return new ReviewedRevision(id, getReviewDate(id));
        }

        private Date getReviewDate(AnyObjectId id) {
            // Implementation to get review date from external source
            return new Date();
        }
    }
}

Clean Up After Revision Walks

A RevWalk cannot shrink its internal object map. For large traversals, dispose of the walk and create a new one for subsequent operations.

public class RevWalkCleanup {

    public static void processWithCleanup(Repository repository) throws IOException {
        // First walk - loads everything into memory
        RevWalk walk1 = new RevWalk(repository);
        // ... perform walk ...
        walk1.dispose(); // Allows GC to reclaim memory

        // Second walk - fresh start, no accumulated objects
        RevWalk walk2 = new RevWalk(repository);
        // ... perform walk ...
        walk2.dispose();
    }
}

Memory Optimization Guidelines

  1. Balance memory vs. performance: Reusing a RevWalk is faster but retains memory. Creating new walks reclaims memory but rebuilds object maps.
  2. Use setRetainBody(false) when you only need commit topology, not metadata.
  3. Call dispose() on individual commits when you've extracted needed data.
  4. Restrict graph traversal using markUninteresting() to avoid parsing unnecessary commits.
  5. Consider batching: For extremely large repositories, process commits in batches with fresh RevWalk instances between batches.

Next Steps

docs

patterns

index.md

tile.json