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();
}
Related
I am using docx4j api for creating docx file. I am successfully copied one docx content to another.
For copy header content, i get header text.
But my requirement is also copy header image. how can i do this?
I am using below code to copy header-
WordprocessingMLPackage source = WordprocessingMLPackage.load(new File(
"D://PoC//Agenda Formats//test.docx"));
RelationshipsPart rp = source.getMainDocumentPart()
.getRelationshipsPart();
Relationship rel = rp.getRelationshipByType(Namespaces.HEADER);
HeaderPart headerPart = (HeaderPart)rp.getPart(rel);
HeaderPart newHeaderPart = new HeaderPart();
newHeaderPart.setContents(XmlUtils.deepCopy(headerPart.getContents()));
return wordprocessingMLPackage.getMainDocumentPart().addTargetPart(
newHeaderPart, AddPartBehaviour.RENAME_IF_NAME_EXISTS);
but this code not copy image. any help is appreciated.
Try something like (untested):
void attachHeader(HeaderPart sourcePart, WordprocessingMLPackage targetPkg) throws Docx4JException {
HeaderPart newHeaderPart = new HeaderPart();
newHeaderPart.setContents(XmlUtils.deepCopy(sourcePart.getContents()));
if (sourcePart.getRelationshipsPart()!=null) {
// clone the rels part
RelationshipsPart rp = sourcePart.getRelationshipsPart();
newHeaderPart.getRelationshipsPart(true).setContents(XmlUtils.deepCopy(rp.getContents()));
// copy/add each part
for (Relationship r : newHeaderPart.getRelationshipsPart().getContents().getRelationship()) {
// get the source part
Part part = sourcePart.getRelationshipsPart().getPart(r.getId());
// ensure it is loaded
if (part instanceof BinaryPart) {
((BinaryPart)part).getBuffer();
}
// You might need to clone this part depending on your use case, but here I'll just attach it to targetPkg
targetPkg.getParts().getParts().put(part.getPartName(), part);
// This simple approach won't work if the target package already contains a part with the same name
// To fix that, you'd need to rename the part (also in the rel)
part.setPackage(targetPkg);
part.setOwningRelationshipPart(newHeaderPart.getRelationshipsPart());
}
}
targetPkg.getMainDocumentPart().addTargetPart(newHeaderPart,
AddPartBehaviour.RENAME_IF_NAME_EXISTS);
}
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);
}
When we do git log --shortstat we get the number of lines inserted, deleted, and changed. Something like:
1 file changed, 9 insertions(+), 3 deletions(-)
Please help me with getting the number of lines inserted, deleted, and changed.
I am doing a repository clone to get git project on local machine. Here is the same code:
RepoClone repoClone = new RepoClone();
repoClone.repoCloner();
repository = builder.setGitDir(repoClone.repoDir).setMustExist(true).build();
I am even able to get a TreeWalk:
TreeWalk treeWalk = getCommitsTreeWalk();
I am able to retrieve file name, count of number of commits per file, LOC, and the number of developers who worked on each xml/ java file.
while (treeWalk.next()) {
if (treeWalk.getPathString().endsWith(".xml") || treeWalk.getPathString().endsWith(".java")) {
jsonDataset = new JSONObject();
countDevelopers = new HashSet<String>();
count = 0;
logs = new Git(repository).log().addPath(treeWalk.getPathString()).call();
for (RevCommit rev: logs) {
countDevelopers.add(rev.getAuthorIdent().getEmailAddress());
count++;
}
jsonDataset.put("FileName", treeWalk.getPathString());
jsonDataset.put("CountDevelopers", countDevelopers.size());
jsonDataset.put("CountCommits", count);
jsonDataset.put("LOC", countLines(treeWalk.getPathString()));
commitDetails.put(jsonDataset);
}
}
Now, I want to retrieve the number of lines inserted and deleted for each file.
The following code snippet compares two commits and prints the changes. diffFormatter.scan() returns a list of DiffEntrys which each describes an added, deleted or modified file. Each of the diff entries in turn has a list of HunkHeaders which desribe the changes within that file.
// Create two commits to be compared
File file = new File( git.getRepository().getWorkTree(), "file.txt" );
writeFile( file, "line1\n" );
RevCommit oldCommit = commitChanges();
writeFile( file, "line1\nline2\n" );
RevCommit newCommit = commitChanges();
// Obtain tree iterators to traverse the tree of the old/new commit
ObjectReader reader = git.getRepository().newObjectReader();
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
oldTreeIter.reset( reader, oldCommit.getTree() );
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset( reader, newCommit.getTree() );
// Use a DiffFormatter to compare new and old tree and return a list of changes
DiffFormatter diffFormatter = new DiffFormatter( DisabledOutputStream.INSTANCE );
diffFormatter.setRepository( git.getRepository() );
diffFormatter.setContext( 0 );
List<DiffEntry> entries = diffFormatter.scan( newTreeIter, oldTreeIter );
// Print the contents of the DiffEntries
for( DiffEntry entry : entries ) {
System.out.println( entry );
FileHeader fileHeader = diffFormatter.toFileHeader( entry );
List<? extends HunkHeader> hunks = fileHeader.getHunks();
for( HunkHeader hunk : hunks ) {
System.out.println( hunk );
}
}
I think with the information provided by DiffEntry and HunkHeader you should be able to get the desired --shortstat.
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;
}
}
I can't see on the wiki where checking out is documented. Ideally, I would like to check out a file "example/folder/file.xml", if not just the folder... and then when the application closes down or otherwise, be able to commit back in changes to this file. How do I do this?
As SVNKit developer, I would recommend you to prefer new API based on SvnOperationFactory. The old API (based on SVNClientManager) will be operational still but all new SVN features will come only to the new API.
final SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
try {
final SvnCheckout checkout = svnOperationFactory.createCheckout();
checkout.setSingleTarget(SvnTarget.fromFile(workingCopyDirectory));
checkout.setSource(SvnTarget.fromURL(url));
//... other options
checkout.run();
} finally {
svnOperationFactory.dispose();
}
You cannot check out a file in Subversion. You have to check out a folder.
To check out a folder with one or more files:
SVNClientManager ourClientManager = SVNClientManager.newInstance(null,
repository.getAuthenticationManager());
SVNUpdateClient updateClient = ourClientManager.getUpdateClient();
updateClient.setIgnoreExternals(false);
updateClient.doCheckout(url, destPath, revision, revision,
isRecursive);
To commit a previously checked out folder:
SVNClientManager ourClientManager = SVNClientManager.newInstance(null,
repository.getAuthenticationManager());
ourClientManager.getWCClient().doInfo(wcPath, SVNRevision.HEAD);
ourClientManager.getCommitClient().doCommit
(new File[] { wcPath }, keepLocks, commitMessage, false, true);
I also used the code snippet proposed by Dmitry Pavlenko and I had no problems.
But it took nearly 30 minutes to checkout or update a repo struture of 35 MB.
It's not useable in my usecase (simply checking out a directory structure as part of the content/documents/media of a web application).
Or have I made some errors?
final ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(name, password);
final SVNURL svnUrl = SVNURL.create(url.getProtocol(), name, url.getHost(), 443, url.getPath(), true);
SVNRepository svnRepo= SVNRepositoryFactory.create(svnUrl);
svnRepo.setAuthenticationManager(authManager);
svnOperationFactory.setAuthenticationManager(authManager);
SVNDirEntry entry = svnRepo.info(".", -1);
long remoteRevision = entry.getRevision();
if (!workingCopyDirectory.exists()) {
workingCopyDirectory.mkdirs();
}
final SvnCheckout checkout = svnOperationFactory.createCheckout();
checkout.setSource(SvnTarget.fromURL(svnUrl));
checkout.setSingleTarget(SvnTarget.fromFile(workingCopyDirectory));
remoteRevision = checkout.run();