JGit developed and maintained by the Eclipse Foundation is a pure Java implementation of Git and does not depend on a native library or the Git command-line tool.
The library was originally developed for the Eclipse IDE, where it's the main driver of the Git integration. But the library does not depend on the Eclipse IDE and is hosted on the Maven Central repository. It's therefore easy to add JGit to any Java application.
In this blog post, we are going to look at a few JGit examples. I'm not covering every command, just a few everyday Git tasks.
If you are looking for Java examples that are not covered here, check out the following repository:
https://github.com/centic9/jgit-cookbook
In this repository, you find JGit code examples and snippets for every possible Git use case.
In the last section of this blog post, I show you how to create a backup application for your GitHub repositories.
Installation ¶
In a Maven managed project, you add JGit with the following dependency:
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>6.8.0.202311291450-r</version>
</dependency>
<dependency>
The JGit library consists of two parts. It contains classes that implement the low-level part of Git, and on top of this implementation, the library provides a high-level API called Porcelain. The Porcelain API is modeled after the Git command-line tool. If you know the command line command for a specific task, it can be easily translated into JGit code.
The main entry point to the Porcelain API is the class org.eclipse.jgit.api.Git
This class offers methods to construct command classes. Each Porcelain command is represented by one command class.
A command class has setters for all the arguments and options. Each command class provides a call()
method to execute the command.
Methods of command classes are chainable.
Init ¶
The command git init
creates an empty Git repository. This is the first command you run when you want to put a new project under Git control.
To initialize a new Git repository with JGit, you call the static init()
method. The method returns an instance of Git
. Make sure that you close a Git
instance with the close()
method. The Git class implements AutoCloseable so you can wrap the init()
call in an automatic resource management block.
Path repoPath = Paths.get("./my_project");
try (Git git = Git.init().setDirectory(repoPath.toFile()).call()) {
}
setDirectory()
tells the method where to create the new Git repository. init()
creates the directory if it does not exist yet.
You may call init()
on an existing Git repository, it does not delete the history.
As mentioned before, all methods from Git
return a command class. In case of init()
this is the InitCommand
class. Instead of method chaining, you assign the command class to a variable and then call methods one after the other.
InitCommand init = Git.init();
init.setDirectory(repoPath.toFile());
try (Git git = init.call()) {
}
It does not matter which style you use. There is no difference in functionality. Most examples in this blog post use the chained methods style.
Open repository ¶
If you want to work with an existing Git repository, you open it with the static open()
method.
try (Git git = Git.open(repoPath.toFile())) {
}
As the argument, you must pass the file path to the Git repository. This command directly returns an instance of Git
.
Add, remove and commit ¶
With a Git
instance in hand, we can
now run Git commands.
Everyday tasks when working with Git are adding files to the index, removing files from the index, and create commits.
The code in the following example creates two files, adds them to the index, and creates a commit. You
see two methods add()
and commit()
in action.
Files.writeString(repoPath.resolve("file1.md"), "Hello World 1");
git.add().addFilepattern("file1.md").call();
git.commit().setMessage("create file1").setAuthor("author", "author@email.com")
.call();
Files.writeString(repoPath.resolve("file2.md"), "Hello World 2");
git.add().addFilepattern(".").call();
git.commit().setMessage("create file2").setAuthor("author", "author@email.com")
.call();
add()
adds one or multiple files to the index. You specify the files you want to add with the addFilepattern()
method.
In the first example, we add one file with the name file1.md
to the index. In the second example, we specify a directory
(. = current directory). The directory is relative to the path you pass to the open()
method. When you specify
a directory, add()
scans the provided directory and all subdirectories recursively for untracked, not ignored files
and for changed tracked files and adds them to the index.
Notice that addFilepattern()
does not yet support fileglobs (e.g. *.md).
commit()
creates a new commit with the current contents of the index and the given message and author.
In the following example, we change a tracked file. In this case, we may omit the add()
command
and instead call setAll(true)
on the commit command. This is equivalent to the -a
option on the
command line (git commit -a ...
) and tells commit to stage files that
have been modified and deleted automatically. This only affects tracked files. New files always need to be added with add()
first.
Files.writeString(repoPath.resolve("file1.md"), "Hello Earth 1");
git.commit().setAll(true).setMessage("update file1")
.setAuthor("author", "author@email.com").call();
The next example deletes a tracked file. You can either use the -a option (setAll(true)
) to
automatically stage the change before the commit, or you explicitly call rm()
to remove the file from the index and then commit it. rm()
deletes the file in the working tree and index, unless you call setCached(true)
on the rm command.
With this option, rm()
removes the file only from the index.
Files.deleteIfExists(repoPath.resolve("file2.md"));
git.commit().setAll(true).setMessage("delete file2")
.setAuthor("author", "author@email.com").call();
// or
git.rm().addFilepattern("file2.md").call();
git.commit().setMessage("delete file2").setAuthor("author", "author@email.com")
.call();
Log ¶
The log()
method lists the commits of a repository.
The command returns an iterable of RevCommit
instances.
With this object, you have access to commit information like the id, time, message, and author.
The following example lists all commits of the repository. Notice the call of the all()
method.
Iterable<RevCommit> logs = git.log().all().call();
for (RevCommit rev : logs) {
System.out.print(Instant.ofEpochSecond(rev.getCommitTime()));
System.out.print(": ");
System.out.print(rev.getFullMessage());
System.out.println();
System.out.println(rev.getId().getName());
System.out.print(rev.getAuthorIdent().getName());
System.out.println(rev.getAuthorIdent().getEmailAddress());
System.out.println("-------------------------");
}
Instead of listing all commits, an application can limit the list to just commits that affect a particular file or directory.
Return only commits that affect file1.md
.
logs = git.log().addPath("file1.md").call();
Status ¶
git status
is a command that displays the state of your working tree. It displays paths that have differences between the index and the current HEAD commit, paths that have differences between the working tree and the index file, and paths in the working tree that are not tracked (and are not ignored).
The JGit equivalent is git.status().call()
The following example creates a new Git repository and creates multiple files in different states to demonstrate the status command.
The code also shows you how you can programmatically work with the .gitignore
file.
Files.writeString(repoPath.resolve("ignored_file.md"), "Ignore me");
Files.writeString(repoPath.resolve(".gitignore"), "ignored_file.md");
Files.writeString(repoPath.resolve("file1.md"), "Hello World 1");
Files.writeString(repoPath.resolve("file2.md"), "Hello World 2");
Files.writeString(repoPath.resolve("file3.md"), "Hello World 3");
git.add().addFilepattern(".").call();
git.commit().setMessage("create files").setAuthor("author", "author@email.com")
.call();
Files.writeString(repoPath.resolve("file4.md"), "Hello World 4");
Files.writeString(repoPath.resolve("file5.md"), "Hello World 5");
git.add().addFilepattern("file5.md").call();
Files.writeString(repoPath.resolve("file2.md"), "Hello Earth 2");
git.add().addFilepattern("file2.md").call();
// Files.deleteIfExists(repoPath.resolve("file1.md"));
git.rm().addFilepattern("file1.md").call();
Files.deleteIfExists(repoPath.resolve("file3.md"));
Files.createDirectory(repoPath.resolve("new_directory"));
Status status = git.status().call();
System.out.println("Added : " + status.getAdded());
System.out.println("Changed : " + status.getChanged());
System.out.println("Removed : " + status.getRemoved());
System.out.println("Uncommitted Changes: " + status.getUncommittedChanges());
System.out.println("Untracked : " + status.getUntracked());
System.out.println("Untracked Folders : " + status.getUntrackedFolders());
System.out.println("Ignored Not Index : " + status.getIgnoredNotInIndex());
System.out.println("Conflicting : " + status.getConflicting());
System.out.println("Missing : " + status.getMissing());
The status command returns an instance of Status
, which
contains all the differences. Most getter methods of Status
return a set of strings.
- Added: New files that have been added to the index.
- Changed: Changed tracked files that have been added to the index.
- Removed: Tracked files that have been removed from the index.
- UncommitedChanges: Set of files and folders that are known to the repo and changed either in the index or in the working tree (added, changed, removed, and missing).
- Untracked: Files that have never been added to the repository and are not ignored.
- UntrackedFolders: Folders that have never been added to the repository and are not ignored.
- Ignored: Ignored paths listed in .gitignore
- Conflicting: Modified files that were modified by someone else in the meantime.
- Missing: Tracked paths that are in the index but no longer exist in the working tree.
The example above prints out this output:
Added : [file5.md]
Changed : [file2.md]
Removed : [file1.md]
Uncommitted Changes: [file5.md, file3.md, file2.md, file1.md]
Untracked : [file4.md]
Untracked Folders : [new_directory]
Ignored Not Index : [ignored_file.md]
Conflicting : []
Missing : [file3.md]
Another useful method of Status
is isClean()
, which returns true
if there are no differences.
Branch ¶
JGit provides a series of commands that work with branches.
To get the name of the current checked out branch issue the following command:
String currentBranch = git.getRepository().getFullBranch();
Create and checkout branch ¶
You create a branch with the branchCreate()
method. You have to specify the name of the branch with setName()
.
checkout()
updates the files in the working tree to match the branch in the repository.
git.branchCreate().setName("new_feature").call();
git.checkout().setName("new_feature").call();
You can create and checkout a branch with the checkout()
method when you call setCreateBranch(true)
. This option automatically
creates the branch if it does not exist yet.
git.checkout().setName("new_feature").setCreateBranch(true).call();
Merge branch ¶
Usually, after you have finished working on a branch, you want to merge it with another branch.
First checkout the branch you want to merge into
git.checkout().setName("master").call();
Then get an ObjectId to the branch and call the merge()
method.
As an argument, you specify the branch you want to merge with include()
.
ObjectId branchObjectId = git.getRepository().resolve("new_feature");
MergeResult mergeResult = git.merge()
// .setFastForward(MergeCommand.FastForwardMode.NO_FF)
// .setCommit(false)
// .setMessage("merge")
.include(branchObjectId).call();
System.out.println(mergeResult);
if (mergeResult.getConflicts() != null) {
for (Map.Entry<String, int[][]> entry : mergeResult.getConflicts().entrySet()) {
System.out.println("Key: " + entry.getKey());
for (int[] arr : entry.getValue()) {
System.out.println("value: " + Arrays.toString(arr));
}
}
}
The merge command either does a fast-forward merge, by updating the branch pointer without creating a merge commit or incorporates the changes from the other branch into the current branch and creates a commit.
If you always want to create a merge commit, disable fast-forward with setFastForward(MergeCommand.FastForwardMode.NO_FF)
.
And if you don't want to create a commit, disable it with setCommit(false)
.
List branch ¶
The branchList()
method lists all branches. By default, it only returns local branches. You can change that with setListMode()
. ListMode.ALL
returns local and remote branches, ListMode.REMOTE
returns only remote branches.
List<Ref> branches = git.branchList().setListMode(ListMode.ALL).call();
for (Ref branch : branches) {
System.out.println(branch.getName());
}
Rename branch ¶
branchRename()
renames an existing branch.
git.branchRename().setOldName("new_feature").setNewName("amazing_feature").call();
Delete branch ¶
The method branchDelete
deletes a branch.
This command fails when the specified branch is checked out, so you have to check out another branch first.
git.checkout().setName("master").call();
git.branchDelete().setBranchNames("amazing_feature").setForce(true).call();
By default, branchDelete()
checks whether the branch you want to delete is already merged into the current branch. If the branch is not merged, deletion
will be refused. You change this behavior by calling setForce(true)
, in that case, no check will be performed, and the branch will be deleted regardless
if he's merged or not.
Tag ¶
You create tags with the tag()
method.
JGit provides the tagDelete()
method to delete a tag.
setName()
and setMessage()
specify the name and the message of the tag .
With tagList()
you get a list of all tags in the repository.
Files.writeString(repoPath.resolve("file1.md"), "Hello World 1");
git.add().addFilepattern(".").call();
git.commit().setMessage("initial commit").setAuthor("author", "author@email.com")
.call();
git.tag().setName("v1.0").setMessage("version 1.0").call();
Files.writeString(repoPath.resolve("file2.md"), "Hello World 2");
git.add().addFilepattern(".").call();
git.commit().setMessage("new feature").setAuthor("author", "author@email.com")
.call();
git.tag().setName("v1.0.1").setMessage("version 1.0.1").call();
git.tagDelete().setTags("v1.0.1").call();
git.tag().setName("v1.1").setMessage("version 1.1").call();
List<Ref> tags = git.tagList().call();
for (Ref tag : tags) {
System.out.println(tag.getName());
}
Diff ¶
The diff()
method returns a collection of changes between two objects (commits, tags, branches).
The following example opens the repository from the previous tag example and compares the two tags.
The diff()
method requires a reference to an old and new tree. To get this reference
we need an ObjectReader instance, two ObjectId for the two objects we want to compare
and an instance of CanonicalTreeParser for each of the objects.
We then configure these parsers with setNewTree()
and setOldTree()
.
To get the ObjectId, the application calls resolve()
from the Repository class with the name of the tag.
The diff command requires the tree id, which we get with the suffix ^{tree}
.
Path repoPath = Paths.get("./my_project_tag");
try (Git git = Git.open(repoPath.toFile());
ObjectReader reader = git.getRepository().newObjectReader()) {
ObjectId oldObject = git.getRepository().resolve("v1.0^{tree}");
ObjectId newObject = git.getRepository().resolve("v1.1^{tree}");
CanonicalTreeParser oldIter = new CanonicalTreeParser();
oldIter.reset(reader, oldObject);
CanonicalTreeParser newIter = new CanonicalTreeParser();
newIter.reset(reader, newObject);
List<DiffEntry> diffs = git.diff().setNewTree(newIter).setOldTree(oldIter).call();
for (DiffEntry entry : diffs) {
System.out.println("type: " + entry.getChangeType());
System.out.println("old : " + entry.getOldPath());
System.out.println("new : " + entry.getNewPath());
}
}
The diff()
method returns a List of DiffEntry
instances.
Each instance represents one change and contains the type of the change (ADD, MODIFY, DELETE, RENAME, COPY) as well as the old and new path.
There is only one change between the two tags; we added the file file2.md
.
Notice that the old path is /dev/null
because the file did not exist in the old tree.
type: ADD
old : /dev/null
new : file2.md
Let's look at an example with a bit more changes. The following program compares two commits.
To get the reference to the two commits, we use the following code.
ObjectId oldObject = git.getRepository().resolve("HEAD~^{tree}");
ObjectId newObject = git.getRepository().resolve("HEAD^{tree}");
HEAD
references the last commit in our repository and HEAD~
points to the second to last commit.
repoPath = Paths.get("./my_project_diff");
try (Git git = Git.init().setDirectory(repoPath.toFile()).call();
ObjectReader reader = git.getRepository().newObjectReader()) {
Files.writeString(repoPath.resolve("file1.md"), "Hello World 1");
Files.writeString(repoPath.resolve("file2.md"), "Hello World 2");
Files.writeString(repoPath.resolve("file3.md"), "Hello World 3");
Files.writeString(repoPath.resolve("file4.md"), "Hello World 4");
git.add().addFilepattern(".").call();
git.commit().setMessage("initial commit").setAuthor("author", "author@email.com")
.call();
Files.writeString(repoPath.resolve("file1.md"), "Hello Earth 1");
Files.delete(repoPath.resolve("file4.md"));
Files.move(repoPath.resolve("file2.md"), repoPath.resolve("file22.md"));
git.add().addFilepattern(".").call();
git.rm().addFilepattern("file2.md").call();
git.rm().addFilepattern("file4.md").call();
git.commit().setMessage("update").setAuthor("author", "author@email.com").call();
ObjectId oldObject = git.getRepository().resolve("HEAD~^{tree}");
ObjectId newObject = git.getRepository().resolve("HEAD^{tree}");
CanonicalTreeParser oldIter = new CanonicalTreeParser();
oldIter.reset(reader, oldObject);
CanonicalTreeParser newIter = new CanonicalTreeParser();
newIter.reset(reader, newObject);
List<DiffEntry> diffs = git.diff().setNewTree(newIter).setOldTree(oldIter).call();
for (DiffEntry entry : diffs) {
System.out.println("type: " + entry.getChangeType());
System.out.println("old : " + entry.getOldPath());
System.out.println("new : " + entry.getNewPath());
}
}
This code prints the following output. Whenever the file does not exist in one of the trees, the path method returns the string "/dev/null".
type: MODIFY
old : file1.md
new : file1.md
type: DELETE
old : file2.md
new : /dev/null
type: ADD
old : /dev/null
new : file22.md
type: DELETE
old : file4.md
new : /dev/null
JGit provides a special empty tree iterator if you need all the differences from the beginning of the repository to a certain point.
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
List<DiffEntry> diffs = git.diff().setNewTree(newIter).setOldTree(new EmptyTreeIterator()).call();
Add remote and push ¶
When you work with other developers together in a team, usually you not only have your local Git repository but also a remote Git repository where everybody pushes their changes to it and fetches the changes from the other team members.
Before you can push your changes to a remote repository, you need to configure the remote repository
with the remoteAdd
method.
After adding the remote config, you push your repository objects into the remote repository with push()
Path repoPath = Paths.get("./test_repo");
try (Git git = Git.init().setDirectory(repoPath.toFile()).call()) {
Files.writeString(repoPath.resolve("README.md"), "# test_repo");
git.add().addFilepattern("README.md").call();
git.commit().setMessage("first commit").setAuthor("author", "author@email.com")
.call();
// git remote add origin git@github.com:ralscha/test_repo.git
RemoteAddCommand remoteAddCommand = git.remoteAdd();
remoteAddCommand.setName("origin");
remoteAddCommand.setUri(new URIish("git@github.com:ralscha/test_repo.git"));
remoteAddCommand.call();
// git push -u origin master
PushCommand pushCommand = git.push();
pushCommand.add("master");
pushCommand.setRemote("origin");
pushCommand.call();
Usually you need to be authenticated for the push()
command to work. When you work with a remote repository that requires a username/password
authentication you have to configure a UsernamePasswordCredentialsProvider
instance.
pushCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider("username", "password"));
The example above transfers the changes from the local repository to GitHub with SSH. In that case, the application uses public/private key authentication. The SSH library that JGit uses automatically looks for the private key in the user's home directory.
If the SSH private key is stored somewhere else or your key is protected with a passphrase you need to add the following code to configures the underlying SSH connection.
SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
@Override
protected void configure(Host host, Session session) {
// do nothing
}
@Override
protected JSch createDefaultJSch(FS fs) throws JSchException {
JSch defaultJSch = super.createDefaultJSch(fs);
defaultJSch.addIdentity("c:/path/to/my/private_key");
// if key is protected with passphrase
// defaultJSch.addIdentity("c:/path/to/my/private_key", "my_passphrase");
return defaultJSch;
}
};
pushCommand = git.push();
pushCommand.setTransportConfigCallback(transport -> {
SshTransport sshTransport = (SshTransport) transport;
sshTransport.setSshSessionFactory(sshSessionFactory);
});
pushCommand.add("master");
pushCommand.setRemote("origin");
pushCommand.call();
Clone ¶
To clone a remote repository to your local disk, you call the static cloneRepository()
method.
This method requires the URI of the remote repository and a path to a local directory as an argument.
cloneRepository()
creates the local directory if it does not exist before it clones the repository.
Path localPath = Paths.get("./my_other_test_repo");
try (Git git = Git.cloneRepository().setURI("git@github.com:ralscha/test_repo.git")
.setDirectory(localPath.toFile()).call()) {
Files.writeString(localPath.resolve("SECOND.md"), "# another file");
git.add().addFilepattern("SECOND.md").call();
git.commit().setMessage("second commit").setAuthor("author", "author@email.com")
.call();
git.push().call();
}
The example first clones the repository, then adds and commits a second file and pushes the change to the remote repository.
Pull ¶
When you ran the last two examples you have two local repositories "./test_repo" and "./my_other_test_repo" and a remote repository.
In the last example, we pushed a change into the remote repository that is not available in the "./test_repo" repository. Let's see what happens when we try to add another change then push it to the remote repository.
Path repoPath = Paths.get("./test_repo");
try (Git git = Git.open(repoPath.toFile())) {
Files.writeString(repoPath.resolve("THIRD.md"), "# third file");
git.add().addFilepattern("THIRD.md").call();
git.commit().setMessage("third commit").setAuthor("author", "author@email.com")
.call();
Iterable<PushResult> pushResults = git.push().call();
for (PushResult pushResult : pushResults) {
for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) {
System.out.println(update.getStatus());
}
}
When we run this command, we get a push status of "REJECTED_NONFASTFORWARD". That makes sense because we can only
push changes when our local repository is in sync with the remote repository. For that to happen, we have to
pull all changes from the remote repository into our local repository. In JGit, the method pull()
performs this task.
pull is a combination of the fetch and the merge command. That's the reason the PullResult object references the FetchResult and MergeResult instances. These result objects give us detailed information about what files have been changed and the state of the operations. In this case, merge does a FAST_FORWARD merge.
PullResult result = git.pull().call();
FetchResult fetchResult = result.getFetchResult();
MergeResult mergeResult = result.getMergeResult();
MergeStatus mergeStatus = mergeResult.getMergeStatus();
for (TrackingRefUpdate update : fetchResult.getTrackingRefUpdates()) {
System.out.println(update.getLocalName());
System.out.println(update.getRemoteName());
System.out.println(update.getResult());
}
After a successful pull, we can now push our change from before to the remote repository.
The following push()
command returns the status "OK".
pushResults = git.push().call();
for (PushResult pushResult : pushResults) {
for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) {
System.out.println(update.getStatus());
}
}
JGit Use Case: GitHub Backup ¶
In this section, we create a simple backup application for GitHub. It clones all public repositories of a particular user to our disk and compresses them into a zip file.
The first task is to get a list of all public repository of a user. This is quite easy because GitHub provides an HTTP API to access this kind of information.
The endpoint we need is called List user repositories
You don't need an API key, and this particular endpoint can also be accessed anonymously.
To check it out, enter the following URL into your browser, replace :username
with an existing GitHub user name,
and you get back a JSON. This endpoint only returns public repositories.
https://api.github.com/users/:username/repos
We could now write Java code that sends a GET request to this endpoint and parses the response with a JSON parser. But there is an even simpler solution. The Eclipse developers wrote a client library for the GitHub HTTP API. All you have to do is add the following dependency to your project
<groupId>org.eclipse.mylyn.github</groupId>
<artifactId>org.eclipse.egit.github.core</artifactId>
<version>2.1.5</version>
</dependency>
</dependencies>
To access the endpoint, we have to instantiate the class org.eclipse.egit.github.core.service.RepositoryService
first, call the method getRepositories()
and pass
the username as an argument. Under the hood, the library sends a GET request to GitHub and parses the response into a collection of Repository
instances.
RepositoryService service = new RepositoryService();
for (Repository repo : service.getRepositories(user)) {
The Repository
object contains the name of the repository and the clone URL.
We can now easily implement the part of the application that clones all the repositories.
The application clones the repositories into a folder specified with the backupDirectory
parameter.
Before the application clones the repository, it checks if the backup folder already
contains a copy of the repository. The isValidLocalRepository()
method shows you how to perform this check with JGit. The method checks if the repository contains a valid
Git object database and returns true, otherwise false if the directory does not exist or
does not contain a Git repository.
If the backup folder already contains a copy of the repository, the program calls
the fetch()
method to download only the latest changes.
When the repository is not cloned yet, the application calls cloneRepository()
.
The difference to the clone example we saw in the example section is that we clone the repository
as a bare repository (setBare(true)
). A bare repository only contains the Git repository,
the content of the .git
folder, whereas a non-bare repository contains the Git repository and a checked-out working tree.
Because bare repositories don't contain a working tree they use less disk space and are, therefore, a perfect fit for our backup program.
public static void backup(String user, Path backupDirectory) throws IOException,
InvalidRemoteException, TransportException, IllegalStateException, GitAPIException {
Files.createDirectories(backupDirectory);
RepositoryService service = new RepositoryService();
for (Repository repo : service.getRepositories(user)) {
Path repoDir = backupDirectory.resolve(repo.getName());
Files.createDirectories(repoDir);
if (isValidLocalRepository(repoDir)) {
System.out.println("fetching : " + repo.getName());
Git.open(repoDir.toFile()).fetch().call();
}
else {
System.out.println("cloning : " + repo.getName());
Git.cloneRepository().setBare(true).setURI(repo.getCloneUrl())
.setDirectory(repoDir.toFile()).call();
}
}
}
private static boolean isValidLocalRepository(Path repoDir) {
try (FileRepository fileRepository = new FileRepository(repoDir.toFile())) {
return fileRepository.getObjectDatabase().exists();
}
catch (IOException e) {
return false;
}
}
After the application cloned all the repositories, it compresses all files into a zip file with the following code:
private static void compress(Path backupDirectory, Path archive) throws IOException {
try (OutputStream os = Files.newOutputStream(archive);
ZipOutputStream zipOS = new ZipOutputStream(os)) {
zipOS.setLevel(Deflater.BEST_COMPRESSION);
Files.walk(backupDirectory).filter(path -> !Files.isDirectory(path))
.forEach(path -> {
try {
ZipEntry zipEntry = new ZipEntry(
backupDirectory.relativize(path).toString());
zipOS.putNextEntry(zipEntry);
Files.copy(path, zipOS);
zipOS.closeEntry();
}
catch (IOException e) {
e.printStackTrace();
}
});
}
}
To simplify the deployment of the tool, we create a fat jar
that can be called from the command line with java -jar ...jar
.
In a Maven application, we use the shade plugin for this purpose.
I had to add a filter configuration that removes signature files from signed jars. The JGit jar is signed, and the application throws an error if you repackage it into a fat jar.
<finalName>githubbackup</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>ch.rasc.backup.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
On the command line, you build the application with ./mvnw package
or .\mvnw.cmd package
on Windows.
This command compiles the application, then calls the shade plugin, which bundles all 3rd party libraries into
one jar and inserts a MANIFEST.mf file into the jar with the path to the main class.
After a build, you find the bundled jar in the target folder. Start the application with the following command
java -jar githubbackup.jar github_username backup_folder backup.7z
This concludes my blog post about JGit. If you want to learn more about JGit check out the following resources:
Blog posts about JGit
- https://www.codeaffine.com/2015/12/15/getting-started-with-jgit/
- https://www.codeaffine.com/2016/06/16/jgit-diff/
- https://www.codeaffine.com/2014/12/09/jgit-authentication/
JGit code examples and snippets