JGit detect rename in working copy - java

Contex
I'm trying to detect possible file rename that occurred after last commit, in a working copy.
On my example, I have a clean working copy and I do that:
git mv old.txt new.txt
Running $ git status shows the expected result:
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# renamed: old.txt -> new.txt
I tried
Using a StatusCommand, I can see old.txt in the removed list, and new.txt in the added list.
But I can't find a way to link them together.
I'm aware of the existence of RenameDetector, but it works using DiffEntry, and I don't know how to get DiffEntries between HEAD and the Working Copy.

Never mind, found the answer.
JGit's API is very complicated..
TreeWalk tw = new TreeWalk(repository);
tw.setRecursive(true);
tw.addTree(CommitUtils.getHead(repository).getTree());
tw.addTree(new FileTreeIterator(repository));
RenameDetector rd = new RenameDetector(repository);
rd.addAll(DiffEntry.scan(tw));
List<DiffEntry> lde = rd.compute(tw.getObjectReader(), null);
for (DiffEntry de : lde) {
if (de.getScore() >= rd.getRenameScore()) {
System.out.println("file: " + de.getOldPath() + " copied/moved to: " + de.getNewPath());
}
}
(This snippet also use Gitective library)

In a case that someone wants to use path filter when getting DiffEntry, new and old path should be provided.
List<DiffEntry> diffs = git.diff()
.setOldTree(prepareTreeParser(repository, oldCommit))
.setNewTree(prepareTreeParser(repository, newCommit))
.setPathFilter(PathFilterGroup.createFromStrings(new String[]{"new/b.txt","b.txt"}))
.call();
RenameDetector rd = new RenameDetector(repository);
rd.addAll(diffs);
diffs = rd.compute();
If you want code of tree parser method:
private static AbstractTreeIterator prepareTreeParser(Repository repository, String objectId) throws IOException {
try (RevWalk walk = new RevWalk(repository)) {
RevCommit commit = walk.parseCommit(repository.resolve(objectId));
RevTree tree = walk.parseTree(commit.getTree().getId());
CanonicalTreeParser treeParser = new CanonicalTreeParser();
try (ObjectReader reader = repository.newObjectReader()) {
treeParser.reset(reader, tree.getId());
}
walk.dispose();
return treeParser;
}
}

Related

How to programmatically create a GitHub repository?

I have to use Java to programmatically create a GitHub repository and push code to it. Please advise on the best method to use. Also please share any code snippet or related links for the utility.
I referred jgit library, has anyone used it? I also referred hub, gh and command line utility.
You can use the GitHub Rest API.
Generate a Personal Access Token from Settings > Developers Settings > Personal Access Tokens
Once generated use that to call the endpoint -
https://api.github.com/user/repo
with body
{"name": "REPO_NAME"}
and Header
Authorization: token PERSONAL_ACCESS_TOKEN
Example Curl:
curl -H "Authorization: token PERSONAL_ACCESS TOKEN" https://api.github.com/user/repos -d '{"name": "REPO_NAME"}'
Reference Doc: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-for-the-authenticated-user
The gitlab4j api is a great library to do so : https://github.com/gitlab4j/gitlab4j-api
<!-- https://mvnrepository.com/artifact/org.gitlab4j/gitlab4j-api -->
<dependency>
<groupId>org.gitlab4j</groupId>
<artifactId>gitlab4j-api</artifactId>
<version>5.0.1</version>
</dependency>
I'm using to read files from repo, commit and create merge requests :
try {
var branchName = "BRANCH-" + random.nextLong();
var action = new CommitAction();
action.withContent(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(data))
.withFilePath(JSON_FILE_PATH).setAction(Action.UPDATE);
try (var glapi = new GitLabApi(GIT_HOSTNAME, token)) {
glapi.enableRequestResponseLogging(java.util.logging.Level.INFO);
glapi.getCommitsApi().createCommit(REPO_NAME, branchName, message, VERSION, null, null, action);
return glapi.getMergeRequestApi().createMergeRequest(DB_REPO_NAME, branchName, VERSION, message,
message, ADMIN_USER_ID);
}
} catch (Exception ex) {
throw new DataException(ex);
}
how to create a new repo, a new master branch and your first commit :
try (var glapi = new GitLabApi("https://gitlab.com/", token)) {
var projectApi = glapi.getProjectApi();
var project = projectApi.createProject("my-repo");
// my-repo created
var action = new CommitAction();
action.withContent("### ignore some files ###").withFilePath(".gitignore").setAction(Action.CREATE);
glapi.getCommitsApi().createCommit("my-repo", "master", "my first commit", "master", "author#mail.com",
"author", action);
// yoru first commit
}
for github you can use https://github.com/hub4j/github-api
#Test
public void testCreateRepoPublic() throws Exception {
initGithubInstance();
GHUser myself = gitHub.getMyself();
String repoName = "test-repo-public";
GHRepository repo = gitHub.createRepository(repoName).private_(false).create();
try {
assertThat(repo.isPrivate(), is(false));
repo.setPrivate(true);
assertThat(myself.getRepository(repoName).isPrivate(), is(true));
repo.setPrivate(false);
assertThat(myself.getRepository(repoName).isPrivate(), is(false));
} finally {
repo.delete();
}
}
commiting multiple files :
var repo = github.getRepository("my-repo");
GHRef mainRef = repo.getRef("heads/master");
String mainTreeSha = repo.getTreeRecursive("master", 1).getSha();
GHTreeBuilder treeBuilder = repo.createTree().baseTree(mainTreeSha);
treeBuilder.add("file1.txt", Files.readAllBytes(Path.of("file1.txt"), false);
treeBuilder.add("file2.json", Files.readAllBytes(Path.of("file2.json"), false);
treeBuilder.add("dir1/dir2/file3.xml", Files.readAllBytes(Path.of("dir1/dir2/file3.xml"), false);
String treeSha = treeBuilder.create().getSha();
GHCommit commit = repo.createCommit()
.tree(treeSha)
.message("adding multiple files example")
.author("author", "author#mail.com", new Date())
.committer("committer", "committer#mail.com", new Date())
.parent(mainRef.getObject().getSha())
.create();
String commitSha = commit.getSHA1();
mainRef.updateTo(commitSha);
Using eclipse jgit
<!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit -->
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>6.2.0.202206071550-r</version>
</dependency>
Examples :
// to use basic auth
var credentials = new UsernamePasswordCredentialsProvider("user", "password");
// to clone a repository
Git.cloneRepository().setURI("https://github.com/my-repo.git").setCredentialsProvider(credentials).call();
// to init a new repository
Git.init().setDirectory(new File("/opt/my-repo")).setInitialBranch("master").call();
// to use a cloned repo
var git = Git.open(new File("/opt/my-repo"));
// to pull changes from remote
git.pull().setCredentialsProvider(credentials).call();
// to stage one file
git.add().setUpdate(true).addFilepattern("dir/file.txt").call();
// to stage all files in a directory
git.add().setUpdate(true).addFilepattern("dir").call();
// to create a commit with the staged files
git.commit().setMessage("just adding some files.").call();
// to push changes to remote
git.push().call();
I tried the jgit to add files to the GitHub repo. Commit is getting created, but the files are not added, In addfilepattern - I tried "." as well. Below is my code snippet:
Git.cloneRepository().setURI("https://github.com/"+org+"/"+repo+".git").setCredentialsProvider(credentialsProvider).setDirectory(new File(Gitpath)).setCloneAllBranches(true).call();
Git.init().setDirectory(new File(Gitpath)).setInitialBranch("main").call();
var git = Git.open(new File(Gitpath));
git.add().setUpdate(true).addFilepattern(directoryName+fileName).call();
git.commit().setMessage("Adding files.").call();
git.push().setCredentialsProvider(credentialsProvider).call();

How to find who last changed a file with JGit

I need the user who last changed/committed a file in our GIT repository.
The project consists of many subfolders, and the master branch has many merged commits.
I tried :
File gitWorkDir = new File("D:/gitfolder/");
Git git = Git.open(gitWorkDir);
RevCommit latestCommit;
String path = "test.txt";
try( RevWalk revWalk = new RevWalk( git.getRepository() ) ) {
Ref headRef = git.getRepository().exactRef( Constants.HEAD );
RevCommit headCommit = revWalk.parseCommit( headRef.getObjectId());
revWalk.markStart( headCommit );
revWalk.sort( RevSort.COMMIT_TIME_DESC );
TreeFilter filter = AndTreeFilter.create( PathFilter.create( path ), TreeFilter.ANY_DIFF );
revWalk.setTreeFilter(filter);
latestCommit = revWalk.next();
git.close();
}
the Repository and HEAD commit are there, but latestCommit is always null.
Do I have to define the whole path to my file?
Is it a problem that the master branch is so "branched out" so maybe the commit cannot be found, or what else could be the problem?
In git I would use git log --follow path/to/your/file. It gives you the full git history of a specific file (including commit author even if the file name was changed or file was moved to another folder).
For doing this with JGit check out this stackoverflow question.
Espacially this part should be intresting (taken from above answer):
/**
* Returns the result of a git log --follow -- < path >
* #return
* #throws IOException
* #throws MissingObjectException
* #throws GitAPIException
*/
public ArrayList<RevCommit> call() throws IOException, MissingObjectException, GitAPIException {
ArrayList<RevCommit> commits = new ArrayList<RevCommit>();
git = new Git(repository);
RevCommit start = null;
do {
Iterable<RevCommit> log = git.log().addPath(path).call();
for (RevCommit commit : log) {
if (commits.contains(commit)) {
start = null;
} else {
start = commit;
commits.add(commit);
}
}
if (start == null) return commits;
}
while ((path = getRenamedPath( start)) != null);
return commits;
}

How do I do “git show sha1” using JGit

Basically I would like to read the contents of all files in a commit based on the commit hash.
I've tried the following:
try(RevWalk revWalk = new RevWalk(gitRepository))
{
RevCommit commit = revWalk.parseCommit(ObjectId.fromString(commitSha));
RevTree tree = commit.getTree();
try(TreeWalk treeWalk = new TreeWalk(gitRepository))
{
treeWalk.addTree(tree);
treeWalk.setRecursive(true);
ObjectId entryId = null;
while (treeWalk.next())
{
entryId = treeWalk.getObjectId(0);
}
ObjectLoader loader = gitRepository.open(entryId);
}
revWalk.dispose();
}
but it seems to be picking up files from previous commits as well.
EDIT: I realize that I wasn't very specific in my original post.
Let's say I make a commit (Commit1) where I add a file (File1). Then I make a commit (Commit2) where I add a different file (File2). Then I make another commit (Commit3) where I modified File2. I would now like to get the contents of File2 from Commit2 for whatever reason. Using the above, the treewalk will retrieve the contents of Commit2 AND Commit1 which is not what I want.
As you've noticed, Git does not store a commit as a diff to the prior commit, it stores a commit as a snapshot of the entire repository at that point in time.
This is not terribly obvious, because even git show <commitid> will provide you with a diff between a commit and its parent. But it becomes clear when you iterate over the contents of a commit like you've done.
If you want to emulate git show <commitid> and look at what changes were introduced by a commit, you'll need to compare it to its parent.
Git git = new Git(gitRepository);
ObjectId newTreeId = ObjectId.fromString(commitSha + "^{tree}");
ObjectId oldTreeId = gitRepository.resolve(commitSha + "^^{tree}");
CanonicalTreeParser newTree = new CanonicalTreeParser();
newTree.reset(reader, newTreeId);
CanonicalTreeParser oldTree = new CanonicalTreeParser();
oldTree.reset(reader, oldTreeId);
for (DiffEntry de : git.diff().setNewTree(newTree).setOldTree(oldTree).call())
{
/* Print the file diff */
DiffFormatter formatter = new DiffFormatter(System.out);
formatter.setRepository(gitRepository);
formatter.format(de);
}

How to determine who last changed a file with JGit

There is a good cook-book receipt for JGit which describes how to blame the author of a specific line in a file.
Now I want to know who last changed a file. Iterating over all lines to find the last changed line looks a little bit not so elegant. Ideas?
You can use the LogCommand with a path filter like this:
Iterable<RevCommit> iterable = git.log().addPath( "foo.txt" ).call();
RevCommit latestCommit = iterable.iterator().next();
The code looks for the latestCommit that modified foo.txt. I haven't tested the above snippet with merge commits or other commits that have more than one parent.
Note however that this solution potentially may leak resources: the RevWalk which provides the iterator is created by the LogCommand but never closed.
In order to avoid the resource leak you can manually iterate the history like so:
RevCommit latestCommit = null;
String path = "file.txt";
try( RevWalk revWalk = new RevWalk( git.getRepository() ) ) {
Ref headRef = git.getRepository().exactRef( Constants.HEAD );
RevCommit headCommit = revWalk.parseCommit( headRef.getObjectId() );
revWalk.markStart( headCommit );
revWalk.sort( RevSort.COMMIT_TIME_DESC );
revWalk.setTreeFilter( AndTreeFilter.create( PathFilter.create( path ), TreeFilter.ANY_DIFF ) );
latestCommit = revWalk.next();
}

jgit repository browser

I would like to create a git repository browser with jgit. But i don't know how to get the last modified date and the last commit message for a file. Here is my current code for the browser:
File directory = new File("/Users/sdorra/.scm/repositories/git/scm-git");
Repository repository =
RepositoryCache.open(RepositoryCache.FileKey.lenient(directory,
FS.DETECTED), true);
try
{
ObjectId revId = repository.resolve(Constants.HEAD);
DirCache cache = new DirCache(directory, FS.DETECTED);
TreeWalk treeWalk = new TreeWalk(repository);
treeWalk.addTree(new RevWalk(repository).parseTree(revId));
treeWalk.addTree(new DirCacheIterator(cache));
while (treeWalk.next())
{
System.out.println("---------------------------");
System.out.append("name: ").println(treeWalk.getNameString());
System.out.append("path: ").println(treeWalk.getPathString());
ObjectLoader loader = repository.open(treeWalk.getObjectId(0));
System.out.append("directory: ").println(loader.getType()
== Constants.OBJ_TREE);
System.out.append("size: ").println(loader.getSize());
// ???
System.out.append("last modified: ").println("???");
System.out.append("message: ").println("???");
}
}
finally
{
if (repository != null)
{
repository.close();
}
}
It is possible to get the last commit of a file?
Note: My git repository is a bare repository without working copy.
You're using lower level JGit API, why don't you use LogCommand via the org.eclipse.jgit.api package? Then use addPath(...), call()...
After that, you should get a list of RevCommit's for the specified path.

Categories

Resources