public void buildCompositeCommits() throws IOException { revWalk = new RevWalk(repository); ByteArrayOutputStream diffTexts = new ByteArrayOutputStream(); DiffFormatter df = new DiffFormatter(diffTexts); df.setRepository(repository); df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL); df.setContext(0); df.setDiffAlgorithm(DiffAlgorithm.getAlgorithm(SupportedAlgorithm.HISTOGRAM)); df.setDetectRenames(true); for (int idx = 0; idx < _commits.size(); idx++) { RevCommit commit = revWalk.parseCommit(_commits.get(idx)); int p_count = commit.getParentCount(); if (p_count == 0) { throw new RuntimeException("commit with no parent ?!?!"); } RevCommit p = revWalk.parseCommit(commit.getParent(0).getId()); List<DiffEntry> diffs = df.scan(p.getTree(), commit.getTree()); for (DiffEntry d : diffs) { CompositeDiff cd = new CompositeDiff(d, commit); if (ParsingUtils.isSourceFile(d.getOldPath()) || ParsingUtils.isSourceFile(d.getNewPath())) { extractCodeEdits(diffTexts, df, d, cd); } _diffs.add(cd); } } revWalk.release(); }
@Override public boolean include(final RevCommit commit, final Collection<DiffEntry> diffs) throws IOException { for (DiffEntry diff : diffs) { final AbbreviatedObjectId oldId = diff.getOldId(); if (oldId == null) continue; if (EMPTY_BLOB_ID.equals(oldId.toObjectId()) && !EMPTY_BLOB_ID.equals(diff.getNewId().toObjectId())) return true; } return false; }
private DiffEntry findRename(RevCommit parent, RevCommit commit, PathFilter path) throws IOException { if (renameDetector == null) return null; treeWalk.setFilter(TreeFilter.ANY_DIFF); treeWalk.reset(parent.getTree(), commit.getTree()); renameDetector.reset(); renameDetector.addAll(DiffEntry.scan(treeWalk)); for (DiffEntry ent : renameDetector.compute()) { if (isRename(ent) && ent.getNewPath().equals(path.getPath())) return ent; } return null; }
/** * Returns the diff between the two commits for the specified file or folder formatted as a patch. * * @param repository * @param baseCommit if base commit is unspecified, the patch is generated against the primary * parent of the specified commit. * @param commit * @param path if path is specified, the patch is generated only for the specified file or folder. * if unspecified, the patch is generated for the entire diff between the two commits. * @return patch as a string */ public static String getCommitPatch( Repository repository, RevCommit baseCommit, RevCommit commit, String path) { String diff = null; try { final ByteArrayOutputStream os = new ByteArrayOutputStream(); RawTextComparator cmp = RawTextComparator.DEFAULT; PatchFormatter df = new PatchFormatter(os); df.setRepository(repository); df.setDiffComparator(cmp); df.setDetectRenames(true); RevTree commitTree = commit.getTree(); RevTree baseTree; if (baseCommit == null) { if (commit.getParentCount() > 0) { final RevWalk rw = new RevWalk(repository); RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); baseTree = parent.getTree(); } else { // FIXME initial commit. no parent?! baseTree = commitTree; } } else { baseTree = baseCommit.getTree(); } List<DiffEntry> diffEntries = df.scan(baseTree, commitTree); if (path != null && path.length() > 0) { for (DiffEntry diffEntry : diffEntries) { if (diffEntry.getNewPath().equalsIgnoreCase(path)) { df.format(diffEntry); break; } } } else { df.format(diffEntries); } diff = df.getPatch(commit); df.flush(); } catch (Throwable t) { try { error(t, repository, "failed to generate commit diff!"); } catch (Exception e) { } } return diff; }
@Override public void format(DiffEntry ent) throws IOException { currentPath = diffStat.addPath(ent); nofLinesCurrent = 0; isOff = false; entry = ent; if (!truncated) { totalNofLinesPrevious = totalNofLinesCurrent; if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) { truncated = true; isOff = true; } truncateTo = os.size(); } else { isOff = true; } if (truncated) { skipped.add(ent); } else { // Produce a header here and now String path; String id; if (ChangeType.DELETE.equals(ent.getChangeType())) { path = ent.getOldPath(); id = ent.getOldId().name(); } else { path = ent.getNewPath(); id = ent.getNewId().name(); } StringBuilder sb = new StringBuilder( MessageFormat.format( "<div class='header'><div class=\"diffHeader\" id=\"n{0}\"><i class=\"icon-file\"></i> ", id)); sb.append(StringUtils.escapeForHtml(path, false)).append("</div></div>"); sb.append("<div class=\"diff\"><table cellpadding='0'><tbody>\n"); os.write(sb.toString().getBytes()); } // Keep formatting, but if off, don't produce anything anymore. We just keep on counting. super.format(ent); if (!truncated) { // Close the table os.write("</tbody></table></div>\n".getBytes()); } }
@Override public boolean include(final RevCommit commit, final Collection<DiffEntry> diffs) { int count = 0; for (DiffEntry diff : diffs) for (Edit edit : BlobUtils.diff(repository, diff.getOldId().toObjectId(), diff.getNewId().toObjectId())) switch (edit.getType()) { case DELETE: count += edit.getLengthA(); break; case INSERT: case REPLACE: count += edit.getLengthB(); break; default: break; } return include(commit, diffs, count) ? true : include(false); }
private boolean processOne(Candidate n) throws IOException { RevCommit parent = n.getParent(0); if (parent == null) return split(n.getNextCandidate(0), n); revPool.parseHeaders(parent); if (find(parent, n.sourcePath)) { if (idBuf.equals(n.sourceBlob)) return blameEntireRegionOnParent(n, parent); return splitBlameWithParent(n, parent); } if (n.sourceCommit == null) return result(n); DiffEntry r = findRename(parent, n.sourceCommit, n.sourcePath); if (r == null) return result(n); if (0 == r.getOldId().prefixCompare(n.sourceBlob)) { // A 100% rename without any content change can also // skip directly to the parent. n.sourceCommit = parent; n.sourcePath = PathFilter.create(r.getOldPath()); push(n); return false; } Candidate next = n.create(parent, PathFilter.create(r.getOldPath())); next.sourceBlob = r.getOldId().toObjectId(); next.renameScore = r.getScore(); next.loadText(reader); return split(next, n); }
/** * Writes an initial table row containing information about added/removed/renamed/copied files. In * case of a deletion, we also suppress generating the diff; it's not interesting. (All lines * removed.) */ private void handleChange() { // XXX Would be nice if we could generate blob links for the cases handled here. Alas, we lack // the repo // name, and cannot reliably determine it here. We could get the .git directory of a Repository, // if we // passed in the repo, and then take the name of the parent directory, but that'd fail for repos // nested // in GitBlit projects. And we don't know if the repo is inside a project or is a top-level // repo. // // That's certainly solvable (just pass along more information), but would require a larger // rewrite than // I'm prepared to do now. String message; switch (entry.getChangeType()) { case ADD: message = getMsg("gb.diffNewFile", "New file"); break; case DELETE: message = getMsg("gb.diffDeletedFile", "File was deleted"); isOff = true; break; case RENAME: message = MessageFormat.format( getMsg("gb.diffRenamedFile", "File was renamed from {0}"), entry.getOldPath()); break; case COPY: message = MessageFormat.format( getMsg("gb.diffCopiedFile", "File was copied from {0}"), entry.getOldPath()); break; default: return; } writeFullWidthLine(message); }
private Path path(final DiffEntry s) { final ChangeType changeType = s.getChangeType(); switch (changeType) { case DELETE: return Paths.get(s.getOldPath()); case ADD: return Paths.get(s.getNewPath()); case COPY: return Paths.get(s.getNewPath()); case MODIFY: return Paths.get(s.getNewPath()); case RENAME: return Paths.get(s.getNewPath()); default: throw new RuntimeException("Unrecognized change type: " + changeType); } }
/** * Workaround function for complex private methods in DiffFormatter. This sets the html for the * diff headers. * * @return */ public String getHtml() { String html = RawParseUtils.decode(os.toByteArray()); String[] lines = html.split("\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { if (line.startsWith("index")) { // skip index lines } else if (line.startsWith("new file") || line.startsWith("deleted file")) { // skip new file lines } else if (line.startsWith("\\ No newline")) { // skip no new line } else if (line.startsWith("---") || line.startsWith("+++")) { // skip --- +++ lines } else if (line.startsWith("diff")) { // skip diff lines } else { boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit"); if (gitLinkDiff) { sb.append("<tr><th class='diff-line'></th><th class='diff-line'></th>"); if (line.charAt(0) == '+') { sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">"); } else { sb.append( "<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">"); } line = StringUtils.escapeForHtml(line.substring(1), false); } sb.append(line); if (gitLinkDiff) { sb.append("</td></tr>"); } sb.append('\n'); } } if (truncated) { sb.append( MessageFormat.format( "<div class='header'><div class='diffHeader'>{0}</div></div>", StringUtils.escapeForHtml( getMsg("gb.diffTruncated", "Diff truncated after the above file"), false))); // List all files not shown. We can be sure we do have at least one path in skipped. sb.append( "<div class='diff'><table cellpadding='0'><tbody><tr><td class='diff-cell' colspan='4'>"); String deletedSuffix = StringUtils.escapeForHtml(getMsg("gb.diffDeletedFileSkipped", "(deleted)"), false); boolean first = true; for (DiffEntry entry : skipped) { if (!first) { sb.append('\n'); } if (ChangeType.DELETE.equals(entry.getChangeType())) { sb.append( "<span id=\"n" + entry.getOldId().name() + "\">" + StringUtils.escapeForHtml(entry.getOldPath(), false) + ' ' + deletedSuffix + "</span>"); } else { sb.append( "<span id=\"n" + entry.getNewId().name() + "\">" + StringUtils.escapeForHtml(entry.getNewPath(), false) + "</span>"); } first = false; } skipped.clear(); sb.append("</td></tr></tbody></table></div>"); } return sb.toString(); }
public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) { List<PathChangeModel> list = new ArrayList<PathChangeModel>(); if (!hasCommits(repository)) { return list; } RevWalk rw = new RevWalk(repository); try { if (commit == null) { ObjectId object = getDefaultBranch(repository); commit = rw.parseCommit(object); } if (commit.getParentCount() == 0) { TreeWalk tw = new TreeWalk(repository); tw.reset(); tw.setRecursive(true); tw.addTree(commit.getTree()); while (tw.next()) { list.add( new PathChangeModel( tw.getPathString(), tw.getPathString(), 0, tw.getRawMode(0), commit.getId().getName(), ChangeType.ADD)); } tw.release(); } else { RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE); df.setRepository(repository); df.setDiffComparator(RawTextComparator.DEFAULT); df.setDetectRenames(true); List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree()); for (DiffEntry diff : diffs) { if (diff.getChangeType().equals(ChangeType.DELETE)) { list.add( new PathChangeModel( diff.getOldPath(), diff.getOldPath(), 0, diff.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType())); } else { list.add( new PathChangeModel( diff.getNewPath(), diff.getNewPath(), 0, diff.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType())); } } } } catch (Throwable t) { // todo Logger.error(t, t.getMessage()); } finally { rw.dispose(); } return list; }
private static boolean isRename(DiffEntry ent) { return ent.getChangeType() == ChangeType.RENAME || ent.getChangeType() == ChangeType.COPY; }
private boolean processMerge(Candidate n) throws IOException { int pCnt = n.getParentCount(); // If any single parent exactly matches the merge, follow only // that one parent through history. ObjectId[] ids = null; for (int pIdx = 0; pIdx < pCnt; pIdx++) { RevCommit parent = n.getParent(pIdx); revPool.parseHeaders(parent); if (!find(parent, n.sourcePath)) continue; if (!(n instanceof ReverseCandidate) && idBuf.equals(n.sourceBlob)) return blameEntireRegionOnParent(n, parent); if (ids == null) ids = new ObjectId[pCnt]; ids[pIdx] = idBuf.toObjectId(); } // If rename detection is enabled, search for any relevant names. DiffEntry[] renames = null; if (renameDetector != null) { renames = new DiffEntry[pCnt]; for (int pIdx = 0; pIdx < pCnt; pIdx++) { RevCommit parent = n.getParent(pIdx); if (ids != null && ids[pIdx] != null) continue; DiffEntry r = findRename(parent, n.sourceCommit, n.sourcePath); if (r == null) continue; if (n instanceof ReverseCandidate) { if (ids == null) ids = new ObjectId[pCnt]; ids[pCnt] = r.getOldId().toObjectId(); } else if (0 == r.getOldId().prefixCompare(n.sourceBlob)) { // A 100% rename without any content change can also // skip directly to the parent. Note this bypasses an // earlier parent that had the path (above) but did not // have an exact content match. For performance reasons // we choose to follow the one parent over trying to do // possibly both parents. n.sourcePath = PathFilter.create(r.getOldPath()); return blameEntireRegionOnParent(n, parent); } renames[pIdx] = r; } } // Construct the candidate for each parent. Candidate[] parents = new Candidate[pCnt]; for (int pIdx = 0; pIdx < pCnt; pIdx++) { RevCommit parent = n.getParent(pIdx); Candidate p; if (renames != null && renames[pIdx] != null) { p = n.create(parent, PathFilter.create(renames[pIdx].getOldPath())); p.renameScore = renames[pIdx].getScore(); p.sourceBlob = renames[pIdx].getOldId().toObjectId(); } else if (ids != null && ids[pIdx] != null) { p = n.create(parent, n.sourcePath); p.sourceBlob = ids[pIdx]; } else { continue; } EditList editList; if (n instanceof ReverseCandidate && p.sourceBlob.equals(n.sourceBlob)) { // This special case happens on ReverseCandidate forks. p.sourceText = n.sourceText; editList = new EditList(0); } else { p.loadText(reader); editList = diffAlgorithm.diff(textComparator, p.sourceText, n.sourceText); } if (editList.isEmpty()) { // Ignoring whitespace (or some other special comparator) can // cause non-identical blobs to have an empty edit list. In // a case like this push the parent alone. if (n instanceof ReverseCandidate) { parents[pIdx] = p; continue; } p.regionList = n.regionList; n.regionList = null; parents[pIdx] = p; break; } p.takeBlame(editList, n); // Only remember this parent candidate if there is at least // one region that was blamed on the parent. if (p.regionList != null) { // Reverse blame requires inverting the regions. This puts // the regions the parent deleted from us into the parent, // and retains the common regions to look at other parents // for deletions. if (n instanceof ReverseCandidate) { Region r = p.regionList; p.regionList = n.regionList; n.regionList = r; } parents[pIdx] = p; } } if (n instanceof ReverseCandidate) { // On a reverse blame report all deletions found in the children, // and pass on to them a copy of our region list. Candidate resultHead = null; Candidate resultTail = null; for (int pIdx = 0; pIdx < pCnt; pIdx++) { Candidate p = parents[pIdx]; if (p == null) continue; if (p.regionList != null) { Candidate r = p.copy(p.sourceCommit); if (resultTail != null) { resultTail.queueNext = r; resultTail = r; } else { resultHead = r; resultTail = r; } } if (n.regionList != null) { p.regionList = n.regionList.deepCopy(); push(p); } } if (resultHead != null) return result(resultHead); return false; } // Push any parents that are still candidates. for (int pIdx = 0; pIdx < pCnt; pIdx++) { if (parents[pIdx] != null) push(parents[pIdx]); } if (n.regionList != null) return result(n); return false; }
protected String doDiff(Git git, String objectId, String baseObjectId, String path) { Repository r = git.getRepository(); /* RevCommit commit = JGitUtils.getCommit(r, objectId); ObjectId current; if (isNotBlank(objectId)) { current = BlobUtils.getId(r, objectId, blobPath); } else { current = CommitUtils.getHead(r).getId(); } ObjectId previous; if (isNotBlank(baseObjectId)) { previous = BlobUtils.getId(r, baseObjectId, blobPath); } else { RevCommit revCommit = CommitUtils.getCommit(r, current); RevCommit[] parents = revCommit.getParents(); if (parents.length == 0) { throw new IllegalArgumentException("No parent commits!"); } else { previous = parents[0]; } } Collection<Edit> changes = BlobUtils.diff(r, previous, current); // no idea how to format Collection<Edit> :) */ RevCommit commit; if (Strings.isNotBlank(objectId)) { commit = CommitUtils.getCommit(r, objectId); } else { commit = CommitUtils.getHead(r); } RevCommit baseCommit = null; if (Strings.isNotBlank(baseObjectId)) { baseCommit = CommitUtils.getCommit(r, baseObjectId); } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); RawTextComparator cmp = RawTextComparator.DEFAULT; DiffFormatter formatter = new DiffFormatter(buffer); formatter.setRepository(r); formatter.setDiffComparator(cmp); formatter.setDetectRenames(true); RevTree commitTree = commit.getTree(); RevTree baseTree; try { if (baseCommit == null) { if (commit.getParentCount() > 0) { final RevWalk rw = new RevWalk(r); RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); rw.dispose(); baseTree = parent.getTree(); } else { // FIXME initial commit. no parent?! baseTree = commitTree; } } else { baseTree = baseCommit.getTree(); } List<DiffEntry> diffEntries = formatter.scan(baseTree, commitTree); if (path != null && path.length() > 0) { for (DiffEntry diffEntry : diffEntries) { if (diffEntry.getNewPath().equalsIgnoreCase(path)) { formatter.format(diffEntry); break; } } } else { formatter.format(diffEntries); } formatter.flush(); return buffer.toString(); } catch (IOException e) { throw new RuntimeIOException(e); } }
/** * Returns the diff between two commits for the specified file. * * @param repository * @param baseCommit if base commit is null the diff is to the primary parent of the commit. * @param commit * @param path if the path is specified, the diff is restricted to that file or folder. if * unspecified, the diff is for the entire commit. * @param outputType * @return the diff */ public static DiffOutput getDiff( Repository repository, RevCommit baseCommit, RevCommit commit, String path, DiffOutputType outputType) { DiffStat stat = null; String diff = null; try { final ByteArrayOutputStream os = new ByteArrayOutputStream(); RawTextComparator cmp = RawTextComparator.DEFAULT; DiffFormatter df; switch (outputType) { case HTML: df = new GitBlitDiffFormatter(os, commit.getName()); break; case PLAIN: default: df = new DiffFormatter(os); break; } df.setRepository(repository); df.setDiffComparator(cmp); df.setDetectRenames(true); RevTree commitTree = commit.getTree(); RevTree baseTree; if (baseCommit == null) { if (commit.getParentCount() > 0) { final RevWalk rw = new RevWalk(repository); RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); rw.dispose(); baseTree = parent.getTree(); } else { // FIXME initial commit. no parent?! baseTree = commitTree; } } else { baseTree = baseCommit.getTree(); } List<DiffEntry> diffEntries = df.scan(baseTree, commitTree); if (path != null && path.length() > 0) { for (DiffEntry diffEntry : diffEntries) { if (diffEntry.getNewPath().equalsIgnoreCase(path)) { df.format(diffEntry); break; } } } else { df.format(diffEntries); } if (df instanceof GitBlitDiffFormatter) { // workaround for complex private methods in DiffFormatter diff = ((GitBlitDiffFormatter) df).getHtml(); stat = ((GitBlitDiffFormatter) df).getDiffStat(); } else { diff = os.toString(); } df.flush(); } catch (Throwable t) { try { error(t, repository, "failed to generate commit diff!"); } catch (Exception e) { } } return new DiffOutput(outputType, diff, stat); }
public String getDiffText(String str) { int filenubs = 0; int lines = 0; FileRepositoryBuilder builder = new FileRepositoryBuilder(); File gitDir = new File("F:/work/demo-rio/.git"); Repository repository = null; try { if (git == null) { git = Git.open(gitDir); } } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { String cid = str; repository = builder.setGitDir(gitDir).readEnvironment().findGitDir().build(); RevWalk walk = new RevWalk(repository); ObjectId objId = ObjectId.fromString(cid); RevCommit commit = walk.parseCommit(objId); System.out.println(commit.getFullMessage()); TreeWalk tw = new TreeWalk(repository); RevCommit[] parent_commits = commit.getParents(); if (parent_commits.length == 0) { throw new Exception("当前只有一个版本"); } ObjectId objId2 = parent_commits[0].toObjectId(); RevCommit paren_commit = walk.parseCommit(objId2); tw.addTree(paren_commit.getTree()); tw.addTree(commit.getTree()); tw.setRecursive(true); ByteArrayOutputStream out = new ByteArrayOutputStream(); DiffFormatter df = new DiffFormatter(out); df.setRepository(git.getRepository()); RenameDetector rd = new RenameDetector(repository); rd.addAll(DiffEntry.scan(tw)); List<DiffEntry> diffEntries = rd.compute(); if (diffEntries != null || diffEntries.size() != 0) { Iterator<DiffEntry> iterator = new ArrayList<DiffEntry>(diffEntries).iterator(); DiffEntry diffEntry = null; while (iterator.hasNext()) { diffEntry = iterator.next(); String changeType = diffEntry.getChangeType().toString(); String type = ""; if (changeType.equals("DELETE")) { type = ConfigUtil.getFileType(diffEntry.getOldPath()); filenubs++; System.out.println(diffEntry.getOldPath()); } else { type = ConfigUtil.getFileType(diffEntry.getNewPath()); filenubs++; System.out.println(diffEntry.getNewPath()); } // 检查文件的后缀 // System.out.println(type); if (fileTypes.contains(type)) { df.format(diffEntry); String diffText = out.toString("UTF-8"); lines += scanDiffText(diffText); } } } } catch (Exception e) { e.printStackTrace(); } return filenubs + "&" + lines; // System.out.println("the changed file nubs: "+ filenubs); // System.out.println("the changed linr nubs: "+ lines); }