SympleGit is a Java-based Git wrapper, co-developed with AI assistance, offering simplicity and ease of extension through AI integration.
- What is SympleGit?
- Installation
- Why Choose SympleGit Over JGit?
- Choosing SympleGit Over JGit: Understanding the Advantages
- The GitCommander API
- The Facilitator API
- SympleGit and Artificial Intelligence
- Extending SympleGit Facilitator API using a Development Pattern (AI-XOSS)
SympleGit is a minimalist yet robust and expandable Java implementation of Git, characterized by three main features:
- It supports direct calls corresponding to Git command line operations.
- It includes wrapper classes for primary Git actions, with straightforward and easy-to-use names.
- It is extendable and customizable using Artificial Intelligence, adhering to the AI-XOSS (AI-Extendable Open Source Software) pattern. More details on this will follow.
SympleGit requires Java version 11 or newer.
<groupId>com.symplegit</groupId>
<artifactId>symplegit</artifactId>
<version>1.0</version>
JGit is an excellent Java implementation of Git, richly featured and well-regarded for its clean and fluent API.
Designed primarily for complete Git support within Eclipse, JGit implements all the sophisticated actions required by end-users developing Java projects.
For those looking to develop a Java editor and integrate Git management, JGit is a recommended choice.
However, JGit's API comes with a learning curve and lacks direct, one-to-one support for CLI actions. Therefore, SympleGit is likely to be a more straightforward option for simple Git integration in many Java projects, particularly those utilizing basic Git functionalities. Let's delve into the details!
Using JGit for staging files typically requires utilizing the API:
// Staging files with JGIT
final File localPath;
// Prepare a new test-repository.
// This uses https://github.com/centic9/jgit-cookbook tutorial
try (Repository repository = CookbookHelper.createNewRepository()) {
localPath = repository.getWorkTree();
try (Git git = new Git(repository)) {
// Files existence tests skipped for the sake of clarity
// run the add-call
git.add()
.addFilepattern("testfile")
.addFilepattern("testFile2")
.call();
System.out.println("Added file " + myFile + " to repository at " + repository.getDirectory());
}
}
In contrast, SympleGit offers two straightforward options:
- Direct invocation using the GitCommander class with command-line interface (CLI) syntax, such as:
git add testfile
. - Utilizing the Facilitator API with the GitAdder class.
The code for direct invocation is simpler:
// Staging files with SympleGit using GitCommander
String repoDirectoryPath = "/path/to/my/git/repository";
final SympleGit sympleGit = SympleGit.custom()
.setDirectory(repoDirectoryPath)
.build();
// Files existence tests skipped for the sake of clarity
GitCommander gitCommander = sympleGit.gitCommander();
// Well, git add testfile testFile2 ;-)
gitCommander.executeGitCommand("git", "add", "testFile", "testFile2");
Alternatively, you can use the GitAdder
class:
// Staging files with SympleGit using GitAdder
String repoDirectoryPath = "/path/to/my/git/repository";
final SympleGit sympleGit = SympleGit.custom()
.setDirectory(repoDirectoryPath)
.build();
GitAdder gitAdder = new GitAdder(sympleGit);
gitAdder.add("testFile", "testFile2");
The one-to-one correspondence with GitCommander
offers significant advantages:
- It simplifies the process for those not well-versed in Git, allowing them to execute necessary Git commands without errors or spending time searching for the correct API parameters. This makes it easier for Git experts to assist non-experts in implementing accurate Git commands. Conversely, it's also convenient for Git experts who are less familiar with Java to integrate Git calls into a Java workflow.
- Given Git's complexity, the ability to directly use CLI commands in Java enables users to execute complex or uncommon commands without the need to program every option in the API. An example is using detailed Git log commands like
git log --graph --abbrev-commit --decorate --date=relative --all --pretty=format:'%h - %ar | %s (%an)%d' --max-count=10
.
If you're not developing a Java Editor, a more straightforward Git implementation that covers all the basic commands could be easier to use.
As a Java developer, a common scenario involves automatically fixing code in a repository after certain operations. This was our primary motivation behind developing SympleGit.
Our goal was simple: to provide an uncomplicated Git implementation that allows for the creation and pushing of new branches after modifying source code. A typical use case, for example, would be replacing all Statement
instances with PreparedStatement
for enhanced SQL Injection protection. You can learn more about this aspect of security at Sqlephant.
The GitCommander API is versatile, allowing the execution of any Git command regardless of the command's complexity or the size of its output.
For standard operations, GitCommander
efficiently handles the command execution and retrieves the results directly.
// List all branches of a repo and print them on console
String repoDirectoryPath = "/path/to/my/git/repository";
final SympleGit sympleGit = SympleGit.custom()
.setDirectory(repoDirectoryPath)
.build();
GitCommander gitCommander = sympleGit.gitCommander();
gitCommander.executeGitCommand("git", "branch", "-a");
if (! gitCommander.isResponseOk()) {
System.out.println("An Error Occured: " + gitCommander.getProcessError());
return;
}
// OK, display branches on console
String[] branches = gitCommander.getProcessOutput().split("\n");
for (String branch : branches) {
System.out.println(branch);
}
In cases involving more extensive data, such as full commit messages and metadata for each commit in large repositories, GitCommander
employs InputStream
to retrieve the output. This approach ensures that even with infinite substantial amounts of data, GitCommander
can efficiently process.
// List full commit messages and metadata for each commit, which can be quite substantial
// for large repositories.
String repoDirectoryPath = "/path/to/my/git/repository";
final SympleGit sympleGit = SympleGit.custom()
.setDirectory(repoDirectoryPath)
.build();
GitCommander gitCommander = sympleGit.gitCommander();
gitCommander.executeGitCommand("git", "--no-pager", "log");
if (!gitCommander.isResponseOk()) {
System.out.println("An Error occured: " + gitCommander.getProcessError());
return;
}
// It's always cautious to test the output
if (gitCommander.getSize() <= 1 * 1024 * 1024) {
// Small output size: use String
String[] lines = gitCommander.getProcessOutput().split("\n");
for (String line : lines) {
System.out.println(line);
}
} else {
// Large output size: use an InputStream
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(gitCommander.getProcessOutputAsInputStream()));) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
SympleGit enables setting a timeout for the Git process with Builder.setTimeout
:
final SympleGit sympleGit = SympleGit.custom()
.setDirectory(repoDirectoryPath)
.setTimeout(300, TimeUnit.SECONDS) // The process will be terminated after 300 seconds
.build();
The Git process is executed within a thread using java.util.concurrent.Future
, enabling controlled termination of operations. However, it's important to note that while this stops the process, the thread itself may continue running until it reaches a natural stopping point.
When the timeout is reached, GitCommander (or the Facilitator API) throws an unchecked exception, UncheckedTimeoutException.
It's a recommended practice to call the close
method on the SympleGit instance to ensure the cleanup of temporary files. SympleGit is designed to be AutoCloseable
.
The Facilitator API consists of a suite of classes designed to encapsulate the GitCommander
, each tailored to specific types of Git operations. This approach streamlines the process of executing various Git commands by providing specialized, easy-to-use wrappers.
Class Name | Purpose |
---|---|
GitAdd | Manages the staging area for changes. |
GitBranchModify | Handles branch-related operations in write mode. |
GitBranchRead | Manages branch-related operations in read mode. |
GitCommit | Facilitates the process of making commits. |
GitDiff | Compares and tracks changes in the repository. |
GitMerge | Handles merging of branches. |
GitRemote | Manages remote repositories and tracks changes. |
GitRepo | Provides functionality for repository-wide operations. |
GitTag | Manages tagging operations in the repository. |
GitVersion | Retrieves information about the installed Git version. |
Utilizing the Facilitator API is straightforward. Refer to the Javadoc for detailed documentation.
// Staging Files & Committing with SympleGit using GitAdd & GitCommit
String repoDirectoryPath = "/path/to/my/git/repository";
// Create a SympleGit instance
final SympleGit sympleGit = SympleGit.custom()
.setDirectory(repoDirectoryPath)
.setTimeout(5, TimeUnit.MINUTES)
.build();
// Create a GitAdd instance to manage staging
GitAdd gitAdd = new GitAdd(sympleGit);
gitAdd.add("testFile1", "testFile2");
// Create a GitCommit instance for making commits
GitCommit gitCommit = new GitCommit(sympleGit);
gitCommit.commitChanges("Modified test files");
// It's advisable to check the result of the commit operation:
if (!gitCommit.isResponseOk()) {
System.out.println("An Error occurred: " + gitCommit.getError());
if (gitCommit.getException() != null) {
System.out.println("An Exception has been raised: " + gitCommit.getError());
}
return;
}
System.out.println("Added test files to the Git repository");
All classes in the Facilitator API were generated using GPT-4 and have not been manually updated since, with the following exceptions:
- Enhancing Javadoc Documentation and Improving code formatting.
- The
getStagedDiffAsStream()
method inGitDiff
was added manually due to the possibility of the command returning a large volume of data.
The generation process for these classes utilized a single, parameterized prompt. This prompt included three specific parameters:
${0}
: Represents the [class name].${1}
: A list of method names, each potentially with self-descriptive parameters, separated by commas.${2}
: The intended purpose of the class.
Moreover, the prompt included source code from SympleGit, which enabled GPT-4 to efficiently produce contextually relevant new code. (Note: Only GPT-4 is supported; the prompt will not work correctly with GPT-3.5 and has not been test with other AI providers.)
For illustration purposes, the template below was employed to generate the GitRepo
class. To keep it simple, the actual source code of the referenced classes has been omitted from this prompt (full prompt URL):
You are a Java expert and a Git expert, world-class.
I will pass you 4 Java classes:
- SympleGit: a class that is the main point of entry, and allows to get GitCommander with SympleGit.getCommander()
- GitCommander: a class that allows passing Git commands and getting output and errors.
- GitWrapper: an interface for Git Wrapper classes
- GitBranchExample: a simplified example of a wrapper class that does an only update and only a read Git operation.
These classes will be used as a guideline for building a new Wrapper class.
I want you to write following these guidelines a ${0} wrapped class that will have these methods:
${1}
in order to wrap these Git operations: ${2}
The values of ${0}, ${1}, and ${2} are at the end of this prompt.
Add a "@author GPT-4" at first Javadoc.
Please include clean & professional Javadoc in the generated class.
Please make sure to use Git commands with the options that do not use a pager or an editor.
(Remember that, if required, "--no-pager" option must follow immediately "git" command.)
Here are the 4 classes:
[content of SympleGit.java]
[content of GitCommander.java]
[content of GitWrapper.java]
[content of GitBranchExample.java]
${0}=GitRepo
${1}=Methods: cloneRepository(repoUrl), initializeRepository(), getRepositoryStatus(), addRemote(name, url), removeRemote(name)
${2}=For repository-wide operations.
The GitBranchExample
serves as a 'generic' example within the Facilitator classes and is the sole class manually written to guide the volatile and session "training" of GPT-4 for our needs.
This template was then applied to all other classes, with modifications limited to the values of the three sub cited parameters:
${0}=GitAdd
${1}=addAll(), add(List<String> files), add(List<File> files)
${2}=Git add operations.
${0}=GitCommit
${1}=Methods: commitChanges(message), amendCommit(), getCommitHistory(), getCommitDetails(commitHash)
${2}=To handle commits.
${0}=GitDiff
${1}=Methods: getDiff(commitHash1, commitHash2), getStagedDiff(), getFileDiff(filePath)
${2}=To compare changes.
${0}=GitMerge
${1}=Methods: mergeBranches(targetBranch, sourceBranch), abortMerge(), getMergeStatus()
${2}=For merging branches.
${0}=GitRemote
${1}=Methods: fetchRemote(remoteName), pushChanges(remoteName, branchName), pullChanges(remoteName, branchName), listRemotes()
${2}=For operations on remote repositories.
${0}=GitRepo
${1}=Methods: cloneRepository(repoUrl), initializeRepository(), getRepositoryStatus(), addRemote(name, url), removeRemote(name)
${2}=For repository-wide operations.
${0}=GitTag
${1}=Methods: createTag(tagName, commitHash), deleteTag(tagName), listTags()
${2}=For tagging operations.
${0}=GitVersion
${1}=getVersion()
${2}=Get Git version.
This approach also extends to all unit tests, which were similarly produced by GPT-4:
You are a Java expert and a Git expert, world-class.
Please write a complete unit test class for the following ${0} Java class.
The value ${0} is at the end of this prompt.
Use this Java line to get the repository File:
File repoDir = GitTestUtils.createIfNotTexistsTemporaryGitRepo();
The class GitTestUtils and the createIfNotTexistsTemporaryGitRepo method already exist, do not create them.
As the repoDir is real and exists as a Git repo, do not use mocking in the code, use only real calls.
${0}=GitRepo
See the source code tests for more info.
The prompt used for generating code with SympleGit is accessible at the following link: GitHub SympleGit Prompt Template.
Curious about what's next? The SympleGit open source software can now be effortlessly extended by simply submitting a prompt to GPT-4, accompanied by the parameters for any new class you aim to create.
For instance, if you want to manage Git configurations, you can resubmit the prompt template with the following three parameter values:
${0}=GitConfig
${1}=Methods: getUserConfig(), setUserConfig(userName, userEmail), getGlobalConfig(), setGlobalConfig(configKey, configValue)
${2}=For managing Git configurations.
This approach of developing open source code with AI, utilizing parametrized templates, is termed "AI-Extendable Open Source Software" or AI-XOSS.
AI-XOSS is a software development pattern that integrates artificial intelligence (AI) capabilities into open-source software projects. This approach allows users, even those with limited or zero AI knowledge, to enhance software using AI-assisted tools.
The AI-XOSS pattern combines AI technologies with open-source development principles, resulting in software that is both adaptive and user-friendly. A key aspect of AI-XOSS is its user-centric design, enabling users to expand software capabilities through AI.
The primary objective of the AI-XOSS pattern is to encourage open-source developers to publish software that is extendable using AI, without requiring users to have any AI expertise, except for knowing how to submit a prompt to a language model like GPT-4.
Utilizing the AI-XOSS pattern offers numerous advantages for both open-source developers and their community of users/developers:
- Open-source software authors can release products that may not include every anticipated feature initially, reducing the time to market.
- Users don't need to request pull requests or wait for the next version; they can extend the software for their specific needs without traditional programming.
- It fosters a collaborative ecosystem where users contribute to the software’s evolution, potentially leading to more innovative and diverse functionalities.
- The pattern can reduce the overall development burden on the core team, as users can independently develop extensions and customizations.
- It democratizes software development, enabling a wider range of contributors to participate in the software creation process.
- AI-XOSS can lead to faster iteration and improvement of software, as AI-generated solutions can be quickly tested and refined.
- This approach can potentially increase the software's adaptability to different use cases and environments, enhancing its versatility and appeal.
- AI-XOSS may attract a broader user base, as the ease of customization and extension can appeal to both technical and non-technical users.