Change-Id: I8f26746a611e9ab955efe8b2597cc81db48fb085tags/v1.4.0
@@ -24,6 +24,7 @@ r20: { | |||
- Added option to render Markdown commit messages (issue-203) | |||
- Added setting to control creating a repository as --shared on Unix servers (issue-263) | |||
- Added setting to globally disable anonymous pushes in the receive pack | |||
- Added a normalized diffstat display to the commit, commitdiff, and compare pages | |||
dependencyChanges: ~ | |||
settings: | |||
- { name: 'git.createRepositoriesShared', defaultValue: 'false' } |
@@ -17,6 +17,7 @@ package com.gitblit.models; | |||
import java.io.Serializable; | |||
import org.eclipse.jgit.diff.DiffEntry; | |||
import org.eclipse.jgit.diff.DiffEntry.ChangeType; | |||
import org.eclipse.jgit.lib.FileMode; | |||
@@ -60,6 +61,12 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
return FileMode.TREE.equals(mode); | |||
} | |||
public boolean isFile() { | |||
return FileMode.REGULAR_FILE.equals(mode) | |||
|| FileMode.EXECUTABLE_FILE.equals(mode) | |||
|| (FileMode.MISSING.equals(mode) && !isSymlink() && !isSubmodule() && !isTree()); | |||
} | |||
@Override | |||
public int hashCode() { | |||
return commitId.hashCode() + path.hashCode(); | |||
@@ -107,12 +114,29 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
private static final long serialVersionUID = 1L; | |||
public ChangeType changeType; | |||
public int insertions; | |||
public int deletions; | |||
public PathChangeModel(String name, String path, long size, int mode, String objectId, | |||
String commitId, ChangeType type) { | |||
super(name, path, size, mode, objectId, commitId); | |||
this.changeType = type; | |||
} | |||
public void update(char op) { | |||
switch (op) { | |||
case '+': | |||
insertions++; | |||
break; | |||
case '-': | |||
deletions++; | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
@Override | |||
public int hashCode() { | |||
@@ -123,5 +147,23 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
public boolean equals(Object o) { | |||
return super.equals(o); | |||
} | |||
public static PathChangeModel from(DiffEntry diff, String commitId) { | |||
PathChangeModel pcm; | |||
if (diff.getChangeType().equals(ChangeType.DELETE)) { | |||
pcm = new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff | |||
.getNewMode().getBits(), diff.getOldId().name(), commitId, diff | |||
.getChangeType()); | |||
} else if (diff.getChangeType().equals(ChangeType.RENAME)) { | |||
pcm = new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff | |||
.getNewMode().getBits(), diff.getNewId().name(), commitId, diff | |||
.getChangeType()); | |||
} else { | |||
pcm = new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff | |||
.getNewMode().getBits(), diff.getNewId().name(), commitId, diff | |||
.getChangeType()); | |||
} | |||
return pcm; | |||
} | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* Copyright 2013 gitblit.com. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.gitblit.utils; | |||
import java.io.IOException; | |||
import org.eclipse.jgit.diff.DiffEntry; | |||
import org.eclipse.jgit.diff.DiffFormatter; | |||
import org.eclipse.jgit.diff.RawText; | |||
import org.eclipse.jgit.util.io.NullOutputStream; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
import com.gitblit.utils.DiffUtils.DiffStat; | |||
/** | |||
* Calculates a DiffStat. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class DiffStatFormatter extends DiffFormatter { | |||
private final DiffStat diffStat; | |||
private PathChangeModel path; | |||
public DiffStatFormatter(String commitId) { | |||
super(NullOutputStream.INSTANCE); | |||
diffStat = new DiffStat(commitId); | |||
} | |||
@Override | |||
public void format(DiffEntry entry) throws IOException { | |||
path = diffStat.addPath(entry); | |||
super.format(entry); | |||
} | |||
@Override | |||
protected void writeLine(final char prefix, final RawText text, final int cur) | |||
throws IOException { | |||
path.update(prefix); | |||
} | |||
public DiffStat getDiffStat() { | |||
return diffStat; | |||
} | |||
} |
@@ -16,6 +16,7 @@ | |||
package com.gitblit.utils; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.Serializable; | |||
import java.text.MessageFormat; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -35,15 +36,16 @@ import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.gitblit.models.AnnotatedLine; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
/** | |||
* DiffUtils is a class of utility methods related to diff, patch, and blame. | |||
* | |||
* | |||
* The diff methods support pluggable diff output types like Gitblit, Gitweb, | |||
* and Plain. | |||
* | |||
* | |||
* @author James Moger | |||
* | |||
* | |||
*/ | |||
public class DiffUtils { | |||
@@ -64,17 +66,116 @@ public class DiffUtils { | |||
return null; | |||
} | |||
} | |||
/** | |||
* Encapsulates the output of a diff. | |||
*/ | |||
public static class DiffOutput implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
public final DiffOutputType type; | |||
public final String content; | |||
public final DiffStat stat; | |||
DiffOutput(DiffOutputType type, String content, DiffStat stat) { | |||
this.type = type; | |||
this.content = content; | |||
this.stat = stat; | |||
} | |||
public PathChangeModel getPath(String path) { | |||
if (stat == null) { | |||
return null; | |||
} | |||
return stat.getPath(path); | |||
} | |||
} | |||
/** | |||
* Class that represents the number of insertions and deletions from a | |||
* chunk. | |||
*/ | |||
public static class DiffStat implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
public final List<PathChangeModel> paths = new ArrayList<PathChangeModel>(); | |||
private final String commitId; | |||
public DiffStat(String commitId) { | |||
this.commitId = commitId; | |||
} | |||
public PathChangeModel addPath(DiffEntry entry) { | |||
PathChangeModel pcm = PathChangeModel.from(entry, commitId); | |||
paths.add(pcm); | |||
return pcm; | |||
} | |||
public int getInsertions() { | |||
int val = 0; | |||
for (PathChangeModel entry : paths) { | |||
val += entry.insertions; | |||
} | |||
return val; | |||
} | |||
public int getDeletions() { | |||
int val = 0; | |||
for (PathChangeModel entry : paths) { | |||
val += entry.deletions; | |||
} | |||
return val; | |||
} | |||
public PathChangeModel getPath(String path) { | |||
PathChangeModel stat = null; | |||
for (PathChangeModel p : paths) { | |||
if (p.path.equals(path)) { | |||
stat = p; | |||
break; | |||
} | |||
} | |||
return stat; | |||
} | |||
@Override | |||
public String toString() { | |||
StringBuilder sb = new StringBuilder(); | |||
for (PathChangeModel entry : paths) { | |||
sb.append(entry.toString()).append('\n'); | |||
} | |||
sb.setLength(sb.length() - 1); | |||
return sb.toString(); | |||
} | |||
} | |||
public static class NormalizedDiffStat implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
public final int insertions; | |||
public final int deletions; | |||
public final int blanks; | |||
NormalizedDiffStat(int insertions, int deletions, int blanks) { | |||
this.insertions = insertions; | |||
this.deletions = deletions; | |||
this.blanks = blanks; | |||
} | |||
} | |||
/** | |||
* Returns the complete diff of the specified commit compared to its primary | |||
* parent. | |||
* | |||
* | |||
* @param repository | |||
* @param commit | |||
* @param outputType | |||
* @return the diff as a string | |||
* @return the diff | |||
*/ | |||
public static String getCommitDiff(Repository repository, RevCommit commit, | |||
public static DiffOutput getCommitDiff(Repository repository, RevCommit commit, | |||
DiffOutputType outputType) { | |||
return getDiff(repository, null, commit, null, outputType); | |||
} | |||
@@ -82,35 +183,35 @@ public class DiffUtils { | |||
/** | |||
* Returns the diff for the specified file or folder from the specified | |||
* commit compared to its primary parent. | |||
* | |||
* | |||
* @param repository | |||
* @param commit | |||
* @param path | |||
* @param outputType | |||
* @return the diff as a string | |||
* @return the diff | |||
*/ | |||
public static String getDiff(Repository repository, RevCommit commit, String path, | |||
public static DiffOutput getDiff(Repository repository, RevCommit commit, String path, | |||
DiffOutputType outputType) { | |||
return getDiff(repository, null, commit, path, outputType); | |||
} | |||
/** | |||
* Returns the complete diff between the two specified commits. | |||
* | |||
* | |||
* @param repository | |||
* @param baseCommit | |||
* @param commit | |||
* @param outputType | |||
* @return the diff as a string | |||
* @return the diff | |||
*/ | |||
public static String getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, | |||
public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, | |||
DiffOutputType outputType) { | |||
return getDiff(repository, baseCommit, commit, null, outputType); | |||
} | |||
/** | |||
* 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 | |||
@@ -120,10 +221,11 @@ public class DiffUtils { | |||
* 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 as a string | |||
* @return the diff | |||
*/ | |||
public static String getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, | |||
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(); | |||
@@ -131,7 +233,7 @@ public class DiffUtils { | |||
DiffFormatter df; | |||
switch (outputType) { | |||
case HTML: | |||
df = new GitBlitDiffFormatter(os); | |||
df = new GitBlitDiffFormatter(os, commit.getName()); | |||
break; | |||
case PLAIN: | |||
default: | |||
@@ -172,6 +274,7 @@ public class DiffUtils { | |||
if (df instanceof GitBlitDiffFormatter) { | |||
// workaround for complex private methods in DiffFormatter | |||
diff = ((GitBlitDiffFormatter) df).getHtml(); | |||
stat = ((GitBlitDiffFormatter) df).getDiffStat(); | |||
} else { | |||
diff = os.toString(); | |||
} | |||
@@ -179,13 +282,14 @@ public class DiffUtils { | |||
} catch (Throwable t) { | |||
LOGGER.error("failed to generate commit diff!", t); | |||
} | |||
return diff; | |||
return new DiffOutput(outputType, diff, stat); | |||
} | |||
/** | |||
* 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 | |||
@@ -242,10 +346,72 @@ public class DiffUtils { | |||
return diff; | |||
} | |||
public static DiffStat getDiffStat(Repository repository, RevCommit commit) { | |||
return getDiffStat(repository, null, commit, null); | |||
} | |||
/** | |||
* Returns the diffstat between the two commits for the specified file or folder. | |||
* | |||
* @param repository | |||
* @param baseCommit | |||
* if base commit is unspecified, the diffstat is generated against | |||
* the primary parent of the specified commit. | |||
* @param commit | |||
* @param path | |||
* if path is specified, the diffstat is generated only for the | |||
* specified file or folder. if unspecified, the diffstat is | |||
* generated for the entire diff between the two commits. | |||
* @return patch as a string | |||
*/ | |||
public static DiffStat getDiffStat(Repository repository, RevCommit baseCommit, | |||
RevCommit commit, String path) { | |||
DiffStat stat = null; | |||
try { | |||
RawTextComparator cmp = RawTextComparator.DEFAULT; | |||
DiffStatFormatter df = new DiffStatFormatter(commit.getName()); | |||
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); | |||
} | |||
stat = df.getDiffStat(); | |||
df.flush(); | |||
} catch (Throwable t) { | |||
LOGGER.error("failed to generate commit diff!", t); | |||
} | |||
return stat; | |||
} | |||
/** | |||
* Returns the list of lines in the specified source file annotated with the | |||
* source commit metadata. | |||
* | |||
* | |||
* @param repository | |||
* @param blobPath | |||
* @param objectId | |||
@@ -276,4 +442,47 @@ public class DiffUtils { | |||
} | |||
return lines; | |||
} | |||
/** | |||
* Normalizes a diffstat to an N-segment display. | |||
* | |||
* @params segments | |||
* @param insertions | |||
* @param deletions | |||
* @return a normalized diffstat | |||
*/ | |||
public static NormalizedDiffStat normalizeDiffStat(final int segments, final int insertions, final int deletions) { | |||
final int total = insertions + deletions; | |||
final float fi = ((float) insertions) / total; | |||
int si; | |||
int sd; | |||
int sb; | |||
if (deletions == 0) { | |||
// only addition | |||
si = Math.min(insertions, segments); | |||
sd = 0; | |||
sb = si < segments ? (segments - si) : 0; | |||
} else if (insertions == 0) { | |||
// only deletion | |||
si = 0; | |||
sd = Math.min(deletions, segments); | |||
sb = sd < segments ? (segments - sd) : 0; | |||
} else if (total <= segments) { | |||
// total churn fits in segment display | |||
si = insertions; | |||
sd = deletions; | |||
sb = segments - total; | |||
} else if ((segments % 2) > 0 && fi > 0.45f && fi < 0.55f) { | |||
// odd segment display, fairly even +/-, use even number of segments | |||
si = Math.round(((float) insertions)/total * (segments - 1)); | |||
sd = segments - 1 - si; | |||
sb = 1; | |||
} else { | |||
si = Math.round(((float) insertions)/total * segments); | |||
sd = segments - si; | |||
sb = 0; | |||
} | |||
return new NormalizedDiffStat(si, sd, sb); | |||
} | |||
} |
@@ -23,12 +23,17 @@ import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.text.MessageFormat; | |||
import org.eclipse.jgit.diff.DiffEntry; | |||
import org.eclipse.jgit.diff.DiffFormatter; | |||
import org.eclipse.jgit.diff.RawText; | |||
import org.eclipse.jgit.util.RawParseUtils; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
import com.gitblit.utils.DiffUtils.DiffStat; | |||
/** | |||
* Generates an html snippet of a diff in Gitblit's style. | |||
* Generates an html snippet of a diff in Gitblit's style, tracks changed paths, | |||
* and calculates diff stats. | |||
* | |||
* @author James Moger | |||
* | |||
@@ -37,13 +42,24 @@ public class GitBlitDiffFormatter extends DiffFormatter { | |||
private final OutputStream os; | |||
private int left, right; | |||
private final DiffStat diffStat; | |||
private PathChangeModel currentPath; | |||
public GitBlitDiffFormatter(OutputStream os) { | |||
private int left, right; | |||
public GitBlitDiffFormatter(OutputStream os, String commitId) { | |||
super(os); | |||
this.os = os; | |||
this.diffStat = new DiffStat(commitId); | |||
} | |||
@Override | |||
public void format(DiffEntry ent) throws IOException { | |||
currentPath = diffStat.addPath(ent); | |||
super.format(ent); | |||
} | |||
/** | |||
* Output a hunk header | |||
* | |||
@@ -109,6 +125,10 @@ public class GitBlitDiffFormatter extends DiffFormatter { | |||
@Override | |||
protected void writeLine(final char prefix, final RawText text, final int cur) | |||
throws IOException { | |||
// update entry diffstat | |||
currentPath.update(prefix); | |||
// output diff | |||
os.write("<tr>".getBytes()); | |||
switch (prefix) { | |||
case '+': | |||
@@ -209,4 +229,8 @@ public class GitBlitDiffFormatter extends DiffFormatter { | |||
sb.append("</table></div>"); | |||
return sb.toString(); | |||
} | |||
public DiffStat getDiffStat() { | |||
return diffStat; | |||
} | |||
} |
@@ -81,7 +81,6 @@ import org.eclipse.jgit.treewalk.filter.PathFilterGroup; | |||
import org.eclipse.jgit.treewalk.filter.PathSuffixFilter; | |||
import org.eclipse.jgit.treewalk.filter.TreeFilter; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.io.DisabledOutputStream; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -901,7 +900,7 @@ public class JGitUtils { | |||
Collections.sort(list); | |||
return list; | |||
} | |||
/** | |||
* Returns the list of files changed in a specified commit. If the | |||
* repository does not exist or is empty, an empty list is returned. | |||
@@ -912,6 +911,21 @@ public class JGitUtils { | |||
* @return list of files changed in a commit | |||
*/ | |||
public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) { | |||
return getFilesInCommit(repository, commit, true); | |||
} | |||
/** | |||
* Returns the list of files changed in a specified commit. If the | |||
* repository does not exist or is empty, an empty list is returned. | |||
* | |||
* @param repository | |||
* @param commit | |||
* if null, HEAD is assumed. | |||
* @param calculateDiffStat | |||
* if true, each PathChangeModel will have insertions/deletions | |||
* @return list of files changed in a commit | |||
*/ | |||
public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit, boolean calculateDiffStat) { | |||
List<PathChangeModel> list = new ArrayList<PathChangeModel>(); | |||
if (!hasCommits(repository)) { | |||
return list; | |||
@@ -936,26 +950,25 @@ public class JGitUtils { | |||
tw.release(); | |||
} else { | |||
RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); | |||
DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE); | |||
DiffStatFormatter df = new DiffStatFormatter(commit.getName()); | |||
df.setRepository(repository); | |||
df.setDiffComparator(RawTextComparator.DEFAULT); | |||
df.setDetectRenames(true); | |||
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree()); | |||
for (DiffEntry diff : diffs) { | |||
String objectId = diff.getNewId().name(); | |||
if (diff.getChangeType().equals(ChangeType.DELETE)) { | |||
list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff | |||
.getNewMode().getBits(), objectId, commit.getId().getName(), diff | |||
.getChangeType())); | |||
} else if (diff.getChangeType().equals(ChangeType.RENAME)) { | |||
list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff | |||
.getNewMode().getBits(), objectId, commit.getId().getName(), diff | |||
.getChangeType())); | |||
} else { | |||
list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff | |||
.getNewMode().getBits(), objectId, commit.getId().getName(), diff | |||
.getChangeType())); | |||
// create the path change model | |||
PathChangeModel pcm = PathChangeModel.from(diff, commit.getName()); | |||
if (calculateDiffStat) { | |||
// update file diffstats | |||
df.format(diff); | |||
PathChangeModel pathStat = df.getDiffStat().getPath(pcm.path); | |||
if (pathStat != null) { | |||
pcm.insertions = pathStat.insertions; | |||
pcm.deletions = pathStat.deletions; | |||
} | |||
} | |||
list.add(pcm); | |||
} | |||
} | |||
} catch (Throwable t) { | |||
@@ -990,20 +1003,8 @@ public class JGitUtils { | |||
List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree()); | |||
for (DiffEntry diff : diffEntries) { | |||
if (diff.getChangeType().equals(ChangeType.DELETE)) { | |||
list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff | |||
.getNewMode().getBits(), diff.getOldId().name(), null, diff | |||
.getChangeType())); | |||
} else if (diff.getChangeType().equals(ChangeType.RENAME)) { | |||
list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff | |||
.getNewMode().getBits(), diff.getNewId().name(), null, diff | |||
.getChangeType())); | |||
} else { | |||
list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff | |||
.getNewMode().getBits(), diff.getNewId().name(), null, diff | |||
.getChangeType())); | |||
} | |||
PathChangeModel pcm = PathChangeModel.from(diff, null); | |||
list.add(pcm); | |||
} | |||
Collections.sort(list); | |||
} catch (Throwable t) { |
@@ -502,4 +502,5 @@ gb.todaysActivityStats = today / {1} commits by {2} authors | |||
gb.todaysActivityNone = today / none | |||
gb.noActivityToday = there has been no activity today | |||
gb.anonymousUser= anonymous | |||
gb.commitMessageRenderer = commit message renderer | |||
gb.commitMessageRenderer = commit message renderer | |||
gb.diffStat = {0} insertions & {1} deletions |
@@ -46,13 +46,13 @@ public class BlobDiffPage extends RepositoryPage { | |||
String diff; | |||
if (StringUtils.isEmpty(baseObjectId)) { | |||
// use first parent | |||
diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML); | |||
diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML).content; | |||
add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class, | |||
WicketUtils.newPathParameter(repositoryName, objectId, blobPath))); | |||
} else { | |||
// base commit specified | |||
RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId); | |||
diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML); | |||
diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML).content; | |||
add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class, | |||
WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId, | |||
blobPath))); |
@@ -38,13 +38,14 @@ | |||
<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div> | |||
<!-- changed paths --> | |||
<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div> | |||
<div class="header"><span wicket:id="diffStat"></span><span style="padding-left:5px;"><wicket:message key="gb.changedFiles">[changed files]</wicket:message></span></div> | |||
<table class="pretty"> | |||
<tr wicket:id="changedPath"> | |||
<td class="changeType"><span wicket:id="changeType">[change type]</span></td> | |||
<td class="path"><span wicket:id="pathName">[commit path]</span></td> | |||
<td class="hidden-phone rightAlign"> | |||
<span class="hidden-tablet" style="padding-right:20px;" wicket:id="diffStat"></span> | |||
<span class="link"> | |||
<a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | |||
</span> |
@@ -32,10 +32,11 @@ import org.eclipse.jgit.revwalk.RevCommit; | |||
import com.gitblit.Constants; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
import com.gitblit.models.GitNote; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
import com.gitblit.models.SubmoduleModel; | |||
import com.gitblit.utils.DiffUtils; | |||
import com.gitblit.utils.DiffUtils.DiffOutput; | |||
import com.gitblit.utils.DiffUtils.DiffOutputType; | |||
import com.gitblit.utils.JGitUtils; | |||
import com.gitblit.wicket.CacheControl; | |||
@@ -43,6 +44,7 @@ import com.gitblit.wicket.CacheControl.LastModified; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.panels.CommitHeaderPanel; | |||
import com.gitblit.wicket.panels.CommitLegendPanel; | |||
import com.gitblit.wicket.panels.DiffStatPanel; | |||
import com.gitblit.wicket.panels.GravatarImage; | |||
import com.gitblit.wicket.panels.LinkPanel; | |||
import com.gitblit.wicket.panels.RefsPanel; | |||
@@ -57,7 +59,7 @@ public class CommitDiffPage extends RepositoryPage { | |||
RevCommit commit = getCommit(); | |||
String diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML); | |||
final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML); | |||
List<String> parents = new ArrayList<String>(); | |||
if (commit.getParentCount() > 0) { | |||
@@ -80,6 +82,15 @@ public class CommitDiffPage extends RepositoryPage { | |||
add(new CommitHeaderPanel("commitHeader", repositoryName, commit)); | |||
// add commit diffstat | |||
int insertions = 0; | |||
int deletions = 0; | |||
for (PathChangeModel pcm : diff.stat.paths) { | |||
insertions += pcm.insertions; | |||
deletions += pcm.deletions; | |||
} | |||
add(new DiffStatPanel("diffStat", insertions, deletions)); | |||
addFullText("fullMessage", commit.getFullMessage()); | |||
// git notes | |||
@@ -103,10 +114,8 @@ public class CommitDiffPage extends RepositoryPage { | |||
add(notesView.setVisible(notes.size() > 0)); | |||
// changed paths list | |||
List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, commit); | |||
add(new CommitLegendPanel("commitLegend", paths)); | |||
ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(paths); | |||
add(new CommitLegendPanel("commitLegend", diff.stat.paths)); | |||
ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(diff.stat.paths); | |||
DataView<PathChangeModel> pathsView = new DataView<PathChangeModel>("changedPath", pathsDp) { | |||
private static final long serialVersionUID = 1L; | |||
int counter; | |||
@@ -117,6 +126,7 @@ public class CommitDiffPage extends RepositoryPage { | |||
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType); | |||
setChangeTypeTooltip(changeType, entry.changeType); | |||
item.add(changeType); | |||
item.add(new DiffStatPanel("diffStat", entry.insertions, entry.deletions, true)); | |||
boolean hasSubmodule = false; | |||
String submodulePath = null; | |||
@@ -172,7 +182,7 @@ public class CommitDiffPage extends RepositoryPage { | |||
} | |||
}; | |||
add(pathsView); | |||
add(new Label("diffText", diff).setEscapeModelStrings(false)); | |||
add(new Label("diffText", diff.content).setEscapeModelStrings(false)); | |||
} | |||
@Override |
@@ -71,7 +71,7 @@ | |||
<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div> | |||
<!-- header --> | |||
<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div> | |||
<div class="header"><span wicket:id="diffStat"></span><span style="padding-left:5px;"><wicket:message key="gb.changedFiles">[changed files]</wicket:message></span></div> | |||
<!-- changed paths --> | |||
<table class="pretty"> | |||
@@ -79,6 +79,7 @@ | |||
<td class="changeType"><span wicket:id="changeType">[change type]</span></td> | |||
<td class="path"><span wicket:id="pathName">[commit path]</span></td> | |||
<td class="hidden-phone rightAlign"> | |||
<span class="hidden-tablet" style="padding-right:20px;" wicket:id="diffStat"></span> | |||
<span class="link"> | |||
<a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | |||
</span> |
@@ -43,6 +43,7 @@ import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.panels.CommitHeaderPanel; | |||
import com.gitblit.wicket.panels.CommitLegendPanel; | |||
import com.gitblit.wicket.panels.CompressedDownloadsPanel; | |||
import com.gitblit.wicket.panels.DiffStatPanel; | |||
import com.gitblit.wicket.panels.GravatarImage; | |||
import com.gitblit.wicket.panels.LinkPanel; | |||
import com.gitblit.wicket.panels.RefsPanel; | |||
@@ -141,6 +142,16 @@ public class CommitPage extends RepositoryPage { | |||
// changed paths list | |||
List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, c); | |||
// add commit diffstat | |||
int insertions = 0; | |||
int deletions = 0; | |||
for (PathChangeModel pcm : paths) { | |||
insertions += pcm.insertions; | |||
deletions += pcm.deletions; | |||
} | |||
add(new DiffStatPanel("diffStat", insertions, deletions)); | |||
add(new CommitLegendPanel("commitLegend", paths)); | |||
ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(paths); | |||
DataView<PathChangeModel> pathsView = new DataView<PathChangeModel>("changedPath", pathsDp) { | |||
@@ -153,7 +164,8 @@ public class CommitPage extends RepositoryPage { | |||
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType); | |||
setChangeTypeTooltip(changeType, entry.changeType); | |||
item.add(changeType); | |||
item.add(new DiffStatPanel("diffStat", entry.insertions, entry.deletions, true)); | |||
boolean hasSubmodule = false; | |||
String submodulePath = null; | |||
if (entry.isTree()) { |
@@ -55,13 +55,14 @@ | |||
<div> | |||
<!-- commit legend --> | |||
<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div> | |||
<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div> | |||
<div class="header"><span wicket:id="diffStat"></span><span style="padding-left:5px;"><wicket:message key="gb.changedFiles">[changed files]</wicket:message></span></div> | |||
</div> | |||
<table class="pretty"> | |||
<tr wicket:id="changedPath"> | |||
<td class="changeType"><span wicket:id="changeType">[change type]</span></td> | |||
<td class="path"><span wicket:id="pathName">[commit path]</span></td> | |||
<td class="hidden-phone rightAlign"> | |||
<span class="hidden-tablet" style="padding-right:20px;" wicket:id="diffStat"></span> | |||
<span class="link"> | |||
<a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | |||
</span> |
@@ -42,12 +42,14 @@ import com.gitblit.models.RefModel; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.SubmoduleModel; | |||
import com.gitblit.utils.DiffUtils; | |||
import com.gitblit.utils.DiffUtils.DiffOutput; | |||
import com.gitblit.utils.DiffUtils.DiffOutputType; | |||
import com.gitblit.utils.JGitUtils; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.wicket.SessionlessForm; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.panels.CommitLegendPanel; | |||
import com.gitblit.wicket.panels.DiffStatPanel; | |||
import com.gitblit.wicket.panels.LinkPanel; | |||
import com.gitblit.wicket.panels.LogPanel; | |||
@@ -108,7 +110,16 @@ public class ComparePage extends RepositoryPage { | |||
fromCommitId.setObject(startId); | |||
toCommitId.setObject(endId); | |||
String diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML); | |||
final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML); | |||
// add compare diffstat | |||
int insertions = 0; | |||
int deletions = 0; | |||
for (PathChangeModel pcm : diff.stat.paths) { | |||
insertions += pcm.insertions; | |||
deletions += pcm.deletions; | |||
} | |||
comparison.add(new DiffStatPanel("diffStat", insertions, deletions)); | |||
// compare page links | |||
// comparison.add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class, | |||
@@ -118,10 +129,8 @@ public class ComparePage extends RepositoryPage { | |||
comparison.add(new LogPanel("commitList", repositoryName, objectId, r, 0, 0, repository.showRemoteBranches)); | |||
// changed paths list | |||
List<PathChangeModel> paths = JGitUtils.getFilesInRange(r, fromCommit, toCommit); | |||
comparison.add(new CommitLegendPanel("commitLegend", paths)); | |||
ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(paths); | |||
comparison.add(new CommitLegendPanel("commitLegend", diff.stat.paths)); | |||
ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(diff.stat.paths); | |||
DataView<PathChangeModel> pathsView = new DataView<PathChangeModel>("changedPath", pathsDp) { | |||
private static final long serialVersionUID = 1L; | |||
int counter; | |||
@@ -132,6 +141,7 @@ public class ComparePage extends RepositoryPage { | |||
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType); | |||
setChangeTypeTooltip(changeType, entry.changeType); | |||
item.add(changeType); | |||
item.add(new DiffStatPanel("diffStat", entry.insertions, entry.deletions, true)); | |||
boolean hasSubmodule = false; | |||
String submodulePath = null; | |||
@@ -185,7 +195,7 @@ public class ComparePage extends RepositoryPage { | |||
} | |||
}; | |||
comparison.add(pathsView); | |||
comparison.add(new Label("diffText", diff).setEscapeModelStrings(false)); | |||
comparison.add(new Label("diffText", diff.content).setEscapeModelStrings(false)); | |||
} | |||
// |
@@ -0,0 +1,12 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" | |||
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | |||
xml:lang="en" | |||
lang="en"> | |||
<wicket:panel> | |||
<span class="hidden-phone hidden-tablet diffstat-total" wicket:id="total">[total]</span> | |||
<span class="diffstat-segments"> | |||
<i class="diffstat-add" wicket:id="insertions"></i><i class="diffstat-delete" wicket:id="deletions"></i><i wicket:id="blank"></i> | |||
</span> | |||
</wicket:panel> | |||
</html> |
@@ -0,0 +1,88 @@ | |||
/* | |||
* Copyright 2013 gitblit.com. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.gitblit.wicket.panels; | |||
import java.text.MessageFormat; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.panel.Panel; | |||
import com.gitblit.utils.DiffUtils; | |||
import com.gitblit.utils.DiffUtils.NormalizedDiffStat; | |||
import com.gitblit.wicket.WicketUtils; | |||
/** | |||
* Display a diffstat. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class DiffStatPanel extends Panel { | |||
private static final long serialVersionUID = 1L; | |||
final int total; | |||
final int insertions; | |||
final int deletions; | |||
final boolean inline; | |||
public DiffStatPanel(String wicketId, int insertions, int deletions) { | |||
this(wicketId, insertions, deletions, false); | |||
} | |||
public DiffStatPanel(String wicketId, int insertions, int deletions, boolean inline) { | |||
super(wicketId); | |||
this.insertions = insertions; | |||
this.deletions = deletions; | |||
this.total = insertions + deletions; | |||
this.inline = inline; | |||
} | |||
@Override | |||
protected void onInitialize() { | |||
super.onInitialize(); | |||
final String diffStat = MessageFormat.format(getString("gb.diffStat"), "" + insertions, "" + deletions); | |||
WicketUtils.setHtmlTooltip(this, diffStat); | |||
final NormalizedDiffStat n = DiffUtils.normalizeDiffStat(5, insertions, deletions); | |||
final String square = "■"; | |||
add(new Label("total", String.valueOf(total))); | |||
add(new Label("insertions", timesRepeat(n.insertions, square)).setEscapeModelStrings(false).setVisible(n.insertions > 0)); | |||
add(new Label("deletions", timesRepeat(n.deletions, square)).setEscapeModelStrings(false).setVisible(n.deletions > 0)); | |||
add(new Label("blank", timesRepeat(n.blanks, square)).setEscapeModelStrings(false).setVisible(n.blanks > 0)); | |||
if (inline) { | |||
WicketUtils.setCssClass(this, "diffstat-inline"); | |||
} else { | |||
WicketUtils.setCssClass(this, "diffstat"); | |||
} | |||
setVisible(total > 0); | |||
} | |||
String timesRepeat(int cnt, String s) { | |||
StringBuilder sb = new StringBuilder(); | |||
for (int i = 0; i < cnt; i++) { | |||
sb.append(s); | |||
} | |||
return sb.toString(); | |||
} | |||
} |
@@ -587,6 +587,45 @@ pre.prettyprint ol { | |||
background-color: #fafafa; | |||
} | |||
.diffstat { | |||
padding: 1px 5px; | |||
font-size: smaller; | |||
background-color: #f5f5f5; | |||
border: 1px solid #ccc; | |||
color: #ccc; | |||
font-weight:bold; | |||
} | |||
.diffstat-inline { | |||
font-size: smaller; | |||
color: #ccc; | |||
font-weight:bold; | |||
} | |||
.diffstat .diffstat-total { | |||
color: black; | |||
border-right: 1px solid #ccc; | |||
padding-right: 4px; | |||
margin-right: 2px; | |||
} | |||
.diffstat-inline .diffstat-total { | |||
color: #999; | |||
padding-right: 2px; | |||
} | |||
.diffstat-segments { | |||
vertical-align: top; | |||
} | |||
.diffstat-add { | |||
color: #629E62; | |||
} | |||
.diffstat-delete { | |||
color: #B9583B; | |||
} | |||
h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { | |||
color: #888; | |||
} |
@@ -43,7 +43,7 @@ public class DiffUtilsTest { | |||
Repository repository = GitBlitSuite.getHelloworldRepository(); | |||
RevCommit commit = JGitUtils.getCommit(repository, | |||
"1d0c2933a4ae69c362f76797d42d6bd182d05176"); | |||
String diff = DiffUtils.getCommitDiff(repository, commit, DiffOutputType.PLAIN); | |||
String diff = DiffUtils.getCommitDiff(repository, commit, DiffOutputType.PLAIN).content; | |||
repository.close(); | |||
assertTrue(diff != null && diff.length() > 0); | |||
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\""; | |||
@@ -57,7 +57,7 @@ public class DiffUtilsTest { | |||
"8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca"); | |||
RevCommit commit = JGitUtils.getCommit(repository, | |||
"1d0c2933a4ae69c362f76797d42d6bd182d05176"); | |||
String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffOutputType.PLAIN); | |||
String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffOutputType.PLAIN).content; | |||
repository.close(); | |||
assertTrue(diff != null && diff.length() > 0); | |||
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\""; | |||
@@ -69,7 +69,7 @@ public class DiffUtilsTest { | |||
Repository repository = GitBlitSuite.getHelloworldRepository(); | |||
RevCommit commit = JGitUtils.getCommit(repository, | |||
"1d0c2933a4ae69c362f76797d42d6bd182d05176"); | |||
String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffOutputType.PLAIN); | |||
String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffOutputType.PLAIN).content; | |||
repository.close(); | |||
assertTrue(diff != null && diff.length() > 0); | |||
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\""; |