Fix for #976 - Filestore links via browsertags/v1.8.0
@@ -94,6 +94,10 @@ public class Constants { | |||
public static final int LEN_SHORTLOG = 78; | |||
public static final int LEN_SHORTLOG_REFS = 60; | |||
public static final int LEN_FILESTORE_META_MIN = 125; | |||
public static final int LEN_FILESTORE_META_MAX = 146; | |||
public static final String DEFAULT_BRANCH = "default"; | |||
@@ -20,6 +20,10 @@ import java.util.ArrayList; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.NoSuchElementException; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import com.gitblit.Constants; | |||
/** | |||
* A FilestoreModel represents a file stored outside a repository but referenced by the repository using a unique objectID | |||
@@ -31,6 +35,18 @@ public class FilestoreModel implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
private static final String metaRegexText = new StringBuilder() | |||
.append("version\\shttps://git-lfs.github.com/spec/v1\\s+") | |||
.append("oid\\ssha256:(" + Constants.REGEX_SHA256 + ")\\s+") | |||
.append("size\\s([0-9]+)") | |||
.toString(); | |||
private static final Pattern metaRegex = Pattern.compile(metaRegexText); | |||
private static final int metaRegexIndexSHA = 1; | |||
private static final int metaRegexIndexSize = 2; | |||
public final String oid; | |||
private Long size; | |||
@@ -43,6 +59,12 @@ public class FilestoreModel implements Serializable { | |||
//Access Control | |||
private List<String> repositories; | |||
public FilestoreModel(String id, long definedSize) { | |||
oid = id; | |||
size = definedSize; | |||
status = Status.ReferenceOnly; | |||
} | |||
public FilestoreModel(String id, long expectedSize, UserModel user, String repo) { | |||
oid = id; | |||
size = expectedSize; | |||
@@ -53,6 +75,29 @@ public class FilestoreModel implements Serializable { | |||
repositories.add(repo); | |||
} | |||
/* | |||
* Attempts to create a FilestoreModel from the given meta string | |||
* | |||
* @return A valid FilestoreModel if successful, otherwise null | |||
*/ | |||
public static FilestoreModel fromMetaString(String meta) { | |||
Matcher m = metaRegex.matcher(meta); | |||
if (m.find()) { | |||
try | |||
{ | |||
final Long size = Long.parseLong(m.group(metaRegexIndexSize)); | |||
final String sha = m.group(metaRegexIndexSHA); | |||
return new FilestoreModel(sha, size); | |||
} catch (Exception e) { | |||
//Fail silent - it is not a valid filestore item | |||
} | |||
} | |||
return null; | |||
} | |||
public synchronized long getSize() { | |||
return size; | |||
} | |||
@@ -102,19 +147,25 @@ public class FilestoreModel implements Serializable { | |||
} | |||
public synchronized void addRepository(String repo) { | |||
if (!repositories.contains(repo)) { | |||
repositories.add(repo); | |||
} | |||
if (status != Status.ReferenceOnly) { | |||
if (!repositories.contains(repo)) { | |||
repositories.add(repo); | |||
} | |||
} | |||
} | |||
public synchronized void removeRepository(String repo) { | |||
repositories.remove(repo); | |||
if (status != Status.ReferenceOnly) { | |||
repositories.remove(repo); | |||
} | |||
} | |||
public synchronized boolean isInRepositoryList(List<String> repoList) { | |||
for (String name : repositories) { | |||
if (repoList.contains(name)) { | |||
return true; | |||
if (status != Status.ReferenceOnly) { | |||
for (String name : repositories) { | |||
if (repoList.contains(name)) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
@@ -122,6 +173,8 @@ public class FilestoreModel implements Serializable { | |||
public static enum Status { | |||
ReferenceOnly(-42), | |||
Deleted(-30), | |||
AuthenticationRequired(-20), | |||
@@ -15,11 +15,20 @@ | |||
*/ | |||
package com.gitblit.models; | |||
import java.io.IOException; | |||
import java.io.Serializable; | |||
import org.eclipse.jgit.diff.DiffEntry; | |||
import org.eclipse.jgit.diff.DiffEntry.ChangeType; | |||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||
import org.eclipse.jgit.errors.MissingObjectException; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.FileMode; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
import com.gitblit.manager.FilestoreManager; | |||
import com.gitblit.utils.JGitUtils; | |||
/** | |||
* PathModel is a serializable model class that represents a file or a folder, | |||
@@ -34,16 +43,18 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
public final String name; | |||
public final String path; | |||
private final FilestoreModel filestoreItem; | |||
public final long size; | |||
public final int mode; | |||
public final String objectId; | |||
public final String commitId; | |||
public boolean isParentPath; | |||
public PathModel(String name, String path, long size, int mode, String objectId, String commitId) { | |||
public PathModel(String name, String path, FilestoreModel filestoreItem, long size, int mode, String objectId, String commitId) { | |||
this.name = name; | |||
this.path = path; | |||
this.size = size; | |||
this.filestoreItem = filestoreItem; | |||
this.size = (filestoreItem == null) ? size : filestoreItem.getSize(); | |||
this.mode = mode; | |||
this.objectId = objectId; | |||
this.commitId = commitId; | |||
@@ -66,6 +77,18 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
|| FileMode.EXECUTABLE_FILE.equals(mode) | |||
|| (FileMode.MISSING.equals(mode) && !isSymlink() && !isSubmodule() && !isTree()); | |||
} | |||
public boolean isFilestoreItem() { | |||
return filestoreItem != null; | |||
} | |||
public String getFilestoreOid() { | |||
if (filestoreItem != null) { | |||
return filestoreItem.oid; | |||
} | |||
return null; | |||
} | |||
@Override | |||
public int hashCode() { | |||
@@ -119,9 +142,9 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
public int deletions; | |||
public PathChangeModel(String name, String path, long size, int mode, String objectId, | |||
public PathChangeModel(String name, String path, FilestoreModel filestoreItem, long size, int mode, String objectId, | |||
String commitId, ChangeType type) { | |||
super(name, path, size, mode, objectId, commitId); | |||
super(name, path, filestoreItem, size, mode, objectId, commitId); | |||
this.changeType = type; | |||
} | |||
@@ -148,18 +171,33 @@ public class PathModel implements Serializable, Comparable<PathModel> { | |||
return super.equals(o); | |||
} | |||
public static PathChangeModel from(DiffEntry diff, String commitId) { | |||
public static PathChangeModel from(DiffEntry diff, String commitId, Repository repository) { | |||
PathChangeModel pcm; | |||
FilestoreModel filestoreItem = null; | |||
long size = 0; | |||
if (repository != null) { | |||
try (RevWalk revWalk = new RevWalk(repository)) { | |||
size = revWalk.getObjectReader().getObjectSize(diff.getNewId().toObjectId(), Constants.OBJ_BLOB); | |||
if (JGitUtils.isPossibleFilestoreItem(size)) { | |||
filestoreItem = JGitUtils.getFilestoreItem(revWalk.getObjectReader().open(diff.getNewId().toObjectId())); | |||
} | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
if (diff.getChangeType().equals(ChangeType.DELETE)) { | |||
pcm = new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff | |||
pcm = new PathChangeModel(diff.getOldPath(), diff.getOldPath(), filestoreItem, size, 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 | |||
pcm = new PathChangeModel(diff.getOldPath(), diff.getNewPath(), filestoreItem, size, diff | |||
.getNewMode().getBits(), diff.getNewId().name(), commitId, diff | |||
.getChangeType()); | |||
} else { | |||
pcm = new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff | |||
pcm = new PathChangeModel(diff.getNewPath(), diff.getNewPath(), filestoreItem, size, diff | |||
.getNewMode().getBits(), diff.getNewId().name(), commitId, diff | |||
.getChangeType()); | |||
} |
@@ -133,10 +133,11 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { | |||
/** | |||
* Allows authentication header to be altered based on the action requested | |||
* Default is WWW-Authenticate | |||
* @param httpRequest | |||
* @param action | |||
* @return authentication type header | |||
*/ | |||
protected String getAuthenticationHeader(String action) { | |||
protected String getAuthenticationHeader(HttpServletRequest httpRequest, String action) { | |||
return "WWW-Authenticate"; | |||
} | |||
@@ -192,7 +193,7 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { | |||
logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl)); | |||
} | |||
httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE); | |||
httpResponse.setHeader(getAuthenticationHeader(httpRequest, urlRequestType), CHALLENGE); | |||
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); | |||
return; | |||
} else { | |||
@@ -239,7 +240,7 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { | |||
if (runtimeManager.isDebugMode()) { | |||
logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl)); | |||
} | |||
httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE); | |||
httpResponse.setHeader(getAuthenticationHeader(httpRequest, urlRequestType), CHALLENGE); | |||
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); | |||
return; | |||
} else { |
@@ -22,6 +22,7 @@ import java.util.Date; | |||
import com.google.inject.Inject; | |||
import com.google.inject.Singleton; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.http.HttpServlet; | |||
import javax.servlet.http.HttpServletResponse; | |||
@@ -34,6 +35,7 @@ import org.slf4j.LoggerFactory; | |||
import com.gitblit.Constants; | |||
import com.gitblit.IStoredSettings; | |||
import com.gitblit.Keys; | |||
import com.gitblit.manager.IFilestoreManager; | |||
import com.gitblit.manager.IRepositoryManager; | |||
import com.gitblit.utils.CompressionUtils; | |||
import com.gitblit.utils.JGitUtils; | |||
@@ -57,6 +59,8 @@ public class DownloadZipServlet extends HttpServlet { | |||
private IStoredSettings settings; | |||
private IRepositoryManager repositoryManager; | |||
private IFilestoreManager filestoreManager; | |||
public static enum Format { | |||
zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2"); | |||
@@ -78,9 +82,10 @@ public class DownloadZipServlet extends HttpServlet { | |||
} | |||
@Inject | |||
public DownloadZipServlet(IStoredSettings settings, IRepositoryManager repositoryManager) { | |||
public DownloadZipServlet(IStoredSettings settings, IRepositoryManager repositoryManager, IFilestoreManager filestoreManager) { | |||
this.settings = settings; | |||
this.repositoryManager = repositoryManager; | |||
this.filestoreManager = filestoreManager; | |||
} | |||
/** | |||
@@ -169,22 +174,23 @@ public class DownloadZipServlet extends HttpServlet { | |||
response.setHeader("Pragma", "no-cache"); | |||
response.setDateHeader("Expires", 0); | |||
try { | |||
switch (format) { | |||
case zip: | |||
CompressionUtils.zip(r, basePath, objectId, response.getOutputStream()); | |||
CompressionUtils.zip(r, filestoreManager, basePath, objectId, response.getOutputStream()); | |||
break; | |||
case tar: | |||
CompressionUtils.tar(r, basePath, objectId, response.getOutputStream()); | |||
CompressionUtils.tar(r, filestoreManager, basePath, objectId, response.getOutputStream()); | |||
break; | |||
case gz: | |||
CompressionUtils.gz(r, basePath, objectId, response.getOutputStream()); | |||
CompressionUtils.gz(r, filestoreManager, basePath, objectId, response.getOutputStream()); | |||
break; | |||
case xz: | |||
CompressionUtils.xz(r, basePath, objectId, response.getOutputStream()); | |||
CompressionUtils.xz(r, filestoreManager, basePath, objectId, response.getOutputStream()); | |||
break; | |||
case bzip2: | |||
CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream()); | |||
CompressionUtils.bzip2(r, filestoreManager, basePath, objectId, response.getOutputStream()); | |||
break; | |||
} | |||
@@ -238,7 +238,10 @@ public class FilestoreServlet extends HttpServlet { | |||
} | |||
} else { | |||
response.setStatus(responseObject.error.code); | |||
serialize(response, responseObject.error); | |||
if (isMetaRequest) { | |||
serialize(response, responseObject.error); | |||
} | |||
} | |||
}; | |||
@@ -102,8 +102,8 @@ public class GitFilter extends AccessRestrictionFilter { | |||
} | |||
/** | |||
* Analyze the url and returns the action of the request. Return values are | |||
* either "/git-receive-pack" or "/git-upload-pack". | |||
* Analyze the url and returns the action of the request. Return values are: | |||
* "/git-receive-pack", "/git-upload-pack" or "/info/lfs". | |||
* | |||
* @param serverUrl | |||
* @return action of the request | |||
@@ -316,18 +316,22 @@ public class GitFilter extends AccessRestrictionFilter { | |||
/** | |||
* Git lfs action uses an alternative authentication header, | |||
* dependent on the viewing method. | |||
* | |||
* @param httpRequest | |||
* @param action | |||
* @return | |||
*/ | |||
@Override | |||
protected String getAuthenticationHeader(String action) { | |||
protected String getAuthenticationHeader(HttpServletRequest httpRequest, String action) { | |||
if (action.equals(gitLfs)) { | |||
return "LFS-Authenticate"; | |||
if (hasContentInRequestHeader(httpRequest, "Accept", FilestoreServlet.GIT_LFS_META_MIME)) { | |||
return "LFS-Authenticate"; | |||
} | |||
} | |||
return super.getAuthenticationHeader(action); | |||
return super.getAuthenticationHeader(httpRequest, action); | |||
} | |||
/** |
@@ -364,7 +364,7 @@ public class RawServlet extends HttpServlet { | |||
if (pathEntries.get(0).path.indexOf('/') > -1) { | |||
// we are in a subdirectory, add parent directory link | |||
String pp = URLEncoder.encode(requestedPath, Constants.ENCODING); | |||
pathEntries.add(0, new PathModel("..", pp + "/..", 0, FileMode.TREE.getBits(), null, null)); | |||
pathEntries.add(0, new PathModel("..", pp + "/..", null, 0, FileMode.TREE.getBits(), null, null)); | |||
} | |||
} | |||
@@ -16,6 +16,9 @@ | |||
package com.gitblit.utils; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.EOFException; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.text.MessageFormat; | |||
@@ -28,9 +31,11 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; | |||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; | |||
import org.apache.commons.compress.compressors.CompressorException; | |||
import org.apache.commons.compress.compressors.CompressorStreamFactory; | |||
import org.apache.commons.io.IOUtils; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.FileMode; | |||
import org.eclipse.jgit.lib.MutableObjectId; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectLoader; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.lib.Repository; | |||
@@ -41,6 +46,11 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.manager.IFilestoreManager; | |||
import com.gitblit.models.FilestoreModel; | |||
import com.gitblit.models.FilestoreModel.Status; | |||
/** | |||
* Collection of static methods for retrieving information from a repository. | |||
* | |||
@@ -87,7 +97,7 @@ public class CompressionUtils { | |||
* @return true if repository was successfully zipped to supplied output | |||
* stream | |||
*/ | |||
public static boolean zip(Repository repository, String basePath, String objectId, | |||
public static boolean zip(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |||
OutputStream os) { | |||
RevCommit commit = JGitUtils.getCommit(repository, objectId); | |||
if (commit == null) { | |||
@@ -115,16 +125,40 @@ public class CompressionUtils { | |||
continue; | |||
} | |||
tw.getObjectId(id, 0); | |||
ObjectLoader loader = repository.open(id); | |||
ZipArchiveEntry entry = new ZipArchiveEntry(tw.getPathString()); | |||
entry.setSize(reader.getObjectSize(id, Constants.OBJ_BLOB)); | |||
FilestoreModel filestoreItem = null; | |||
if (JGitUtils.isPossibleFilestoreItem(loader.getSize())) { | |||
filestoreItem = JGitUtils.getFilestoreItem(tw.getObjectReader().open(id)); | |||
} | |||
final long size = (filestoreItem == null) ? loader.getSize() : filestoreItem.getSize(); | |||
entry.setSize(size); | |||
entry.setComment(commit.getName()); | |||
entry.setUnixMode(mode.getBits()); | |||
entry.setTime(modified); | |||
zos.putArchiveEntry(entry); | |||
if (filestoreItem == null) { | |||
//Copy repository stored file | |||
loader.copyTo(zos); | |||
} else { | |||
//Copy filestore file | |||
try (FileInputStream streamIn = new FileInputStream(filestoreManager.getStoragePath(filestoreItem.oid))) { | |||
IOUtils.copyLarge(streamIn, zos); | |||
} catch (Throwable e) { | |||
LOGGER.error(MessageFormat.format("Failed to archive filestore item {0}", filestoreItem.oid), e); | |||
//Handle as per other errors | |||
throw e; | |||
} | |||
} | |||
ObjectLoader ldr = repository.open(id); | |||
ldr.copyTo(zos); | |||
zos.closeArchiveEntry(); | |||
} | |||
zos.finish(); | |||
@@ -151,9 +185,9 @@ public class CompressionUtils { | |||
* @return true if repository was successfully zipped to supplied output | |||
* stream | |||
*/ | |||
public static boolean tar(Repository repository, String basePath, String objectId, | |||
public static boolean tar(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |||
OutputStream os) { | |||
return tar(null, repository, basePath, objectId, os); | |||
return tar(null, repository, filestoreManager, basePath, objectId, os); | |||
} | |||
/** | |||
@@ -169,9 +203,9 @@ public class CompressionUtils { | |||
* @return true if repository was successfully zipped to supplied output | |||
* stream | |||
*/ | |||
public static boolean gz(Repository repository, String basePath, String objectId, | |||
public static boolean gz(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |||
OutputStream os) { | |||
return tar(CompressorStreamFactory.GZIP, repository, basePath, objectId, os); | |||
return tar(CompressorStreamFactory.GZIP, repository, filestoreManager, basePath, objectId, os); | |||
} | |||
/** | |||
@@ -187,9 +221,9 @@ public class CompressionUtils { | |||
* @return true if repository was successfully zipped to supplied output | |||
* stream | |||
*/ | |||
public static boolean xz(Repository repository, String basePath, String objectId, | |||
public static boolean xz(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |||
OutputStream os) { | |||
return tar(CompressorStreamFactory.XZ, repository, basePath, objectId, os); | |||
return tar(CompressorStreamFactory.XZ, repository, filestoreManager, basePath, objectId, os); | |||
} | |||
/** | |||
@@ -205,10 +239,10 @@ public class CompressionUtils { | |||
* @return true if repository was successfully zipped to supplied output | |||
* stream | |||
*/ | |||
public static boolean bzip2(Repository repository, String basePath, String objectId, | |||
public static boolean bzip2(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |||
OutputStream os) { | |||
return tar(CompressorStreamFactory.BZIP2, repository, basePath, objectId, os); | |||
return tar(CompressorStreamFactory.BZIP2, repository, filestoreManager, basePath, objectId, os); | |||
} | |||
/** | |||
@@ -227,7 +261,7 @@ public class CompressionUtils { | |||
* @return true if repository was successfully zipped to supplied output | |||
* stream | |||
*/ | |||
private static boolean tar(String algorithm, Repository repository, String basePath, String objectId, | |||
private static boolean tar(String algorithm, Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |||
OutputStream os) { | |||
RevCommit commit = JGitUtils.getCommit(repository, objectId); | |||
if (commit == null) { | |||
@@ -263,6 +297,7 @@ public class CompressionUtils { | |||
if (mode == FileMode.GITLINK || mode == FileMode.TREE) { | |||
continue; | |||
} | |||
tw.getObjectId(id, 0); | |||
ObjectLoader loader = repository.open(id); | |||
@@ -278,9 +313,34 @@ public class CompressionUtils { | |||
TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString()); | |||
entry.setMode(mode.getBits()); | |||
entry.setModTime(modified); | |||
entry.setSize(loader.getSize()); | |||
FilestoreModel filestoreItem = null; | |||
if (JGitUtils.isPossibleFilestoreItem(loader.getSize())) { | |||
filestoreItem = JGitUtils.getFilestoreItem(tw.getObjectReader().open(id)); | |||
} | |||
final long size = (filestoreItem == null) ? loader.getSize() : filestoreItem.getSize(); | |||
entry.setSize(size); | |||
tos.putArchiveEntry(entry); | |||
loader.copyTo(tos); | |||
if (filestoreItem == null) { | |||
//Copy repository stored file | |||
loader.copyTo(tos); | |||
} else { | |||
//Copy filestore file | |||
try (FileInputStream streamIn = new FileInputStream(filestoreManager.getStoragePath(filestoreItem.oid))) { | |||
IOUtils.copyLarge(streamIn, tos); | |||
} catch (Throwable e) { | |||
LOGGER.error(MessageFormat.format("Failed to archive filestore item {0}", filestoreItem.oid), e); | |||
//Handle as per other errors | |||
throw e; | |||
} | |||
} | |||
tos.closeArchiveEntry(); | |||
} | |||
} |
@@ -20,6 +20,7 @@ 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.lib.Repository; | |||
import org.eclipse.jgit.util.io.NullOutputStream; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
@@ -37,9 +38,9 @@ public class DiffStatFormatter extends DiffFormatter { | |||
private PathChangeModel path; | |||
public DiffStatFormatter(String commitId) { | |||
public DiffStatFormatter(String commitId, Repository repository) { | |||
super(NullOutputStream.INSTANCE); | |||
diffStat = new DiffStat(commitId); | |||
diffStat = new DiffStat(commitId, repository); | |||
} | |||
@Override |
@@ -157,13 +157,16 @@ public class DiffUtils { | |||
public final List<PathChangeModel> paths = new ArrayList<PathChangeModel>(); | |||
private final String commitId; | |||
private final Repository repository; | |||
public DiffStat(String commitId) { | |||
public DiffStat(String commitId, Repository repository) { | |||
this.commitId = commitId; | |||
this.repository = repository; | |||
} | |||
public PathChangeModel addPath(DiffEntry entry) { | |||
PathChangeModel pcm = PathChangeModel.from(entry, commitId); | |||
PathChangeModel pcm = PathChangeModel.from(entry, commitId, repository); | |||
paths.add(pcm); | |||
return pcm; | |||
} | |||
@@ -379,7 +382,7 @@ public class DiffUtils { | |||
DiffFormatter df; | |||
switch (outputType) { | |||
case HTML: | |||
df = new GitBlitDiffFormatter(commit.getName(), path, handler, tabLength); | |||
df = new GitBlitDiffFormatter(commit.getName(), repository, path, handler, tabLength); | |||
break; | |||
case PLAIN: | |||
default: | |||
@@ -548,7 +551,7 @@ public class DiffUtils { | |||
DiffStat stat = null; | |||
try { | |||
RawTextComparator cmp = RawTextComparator.DEFAULT; | |||
DiffStatFormatter df = new DiffStatFormatter(commit.getName()); | |||
DiffStatFormatter df = new DiffStatFormatter(commit.getName(), repository); | |||
df.setRepository(repository); | |||
df.setDiffComparator(cmp); | |||
df.setDetectRenames(true); |
@@ -33,6 +33,7 @@ import org.eclipse.jgit.diff.DiffEntry; | |||
import org.eclipse.jgit.diff.DiffEntry.ChangeType; | |||
import org.eclipse.jgit.diff.DiffFormatter; | |||
import org.eclipse.jgit.diff.RawText; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.util.RawParseUtils; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
@@ -164,11 +165,11 @@ public class GitBlitDiffFormatter extends DiffFormatter { | |||
} | |||
public GitBlitDiffFormatter(String commitId, String path, BinaryDiffHandler handler, int tabLength) { | |||
public GitBlitDiffFormatter(String commitId, Repository repository, String path, BinaryDiffHandler handler, int tabLength) { | |||
super(new DiffOutputStream()); | |||
this.os = (DiffOutputStream) getOutputStream(); | |||
this.os.setFormatter(this, handler); | |||
this.diffStat = new DiffStat(commitId); | |||
this.diffStat = new DiffStat(commitId, repository); | |||
this.tabLength = tabLength; | |||
// If we have a full commitdiff, install maxima to avoid generating a super-long diff listing that | |||
// will only tax the browser too much. |
@@ -28,6 +28,7 @@ import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Map.Entry; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import org.apache.commons.io.filefilter.TrueFileFilter; | |||
@@ -42,6 +43,7 @@ import org.eclipse.jgit.diff.DiffFormatter; | |||
import org.eclipse.jgit.diff.RawTextComparator; | |||
import org.eclipse.jgit.errors.ConfigInvalidException; | |||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||
import org.eclipse.jgit.errors.LargeObjectException; | |||
import org.eclipse.jgit.errors.MissingObjectException; | |||
import org.eclipse.jgit.errors.StopWalkException; | |||
import org.eclipse.jgit.lib.BlobBasedConfig; | |||
@@ -84,12 +86,16 @@ import org.eclipse.jgit.util.FS; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.GitBlitException; | |||
import com.gitblit.manager.GitblitManager; | |||
import com.gitblit.models.FilestoreModel; | |||
import com.gitblit.models.GitNote; | |||
import com.gitblit.models.PathModel; | |||
import com.gitblit.models.PathModel.PathChangeModel; | |||
import com.gitblit.models.RefModel; | |||
import com.gitblit.models.SubmoduleModel; | |||
import com.gitblit.servlet.FilestoreServlet; | |||
import com.google.common.base.Strings; | |||
/** | |||
@@ -993,23 +999,40 @@ public class JGitUtils { | |||
tw.setRecursive(true); | |||
tw.addTree(commit.getTree()); | |||
while (tw.next()) { | |||
list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw | |||
.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(), | |||
long size = 0; | |||
FilestoreModel filestoreItem = null; | |||
ObjectId objectId = tw.getObjectId(0); | |||
try { | |||
if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) { | |||
size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB); | |||
if (isPossibleFilestoreItem(size)) { | |||
filestoreItem = getFilestoreItem(tw.getObjectReader().open(objectId)); | |||
} | |||
} | |||
} catch (Throwable t) { | |||
error(t, null, "failed to retrieve blob size for " + tw.getPathString()); | |||
} | |||
list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(),filestoreItem, size, tw | |||
.getRawMode(0), objectId.getName(), commit.getId().getName(), | |||
ChangeType.ADD)); | |||
} | |||
tw.close(); | |||
} else { | |||
RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); | |||
DiffStatFormatter df = new DiffStatFormatter(commit.getName()); | |||
DiffStatFormatter df = new DiffStatFormatter(commit.getName(), repository); | |||
df.setRepository(repository); | |||
df.setDiffComparator(RawTextComparator.DEFAULT); | |||
df.setDetectRenames(true); | |||
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree()); | |||
for (DiffEntry diff : diffs) { | |||
// create the path change model | |||
PathChangeModel pcm = PathChangeModel.from(diff, commit.getName()); | |||
if (calculateDiffStat) { | |||
PathChangeModel pcm = PathChangeModel.from(diff, commit.getName(), repository); | |||
if (calculateDiffStat) { | |||
// update file diffstats | |||
df.format(diff); | |||
PathChangeModel pathStat = df.getDiffStat().getPath(pcm.path); | |||
@@ -1083,7 +1106,7 @@ public class JGitUtils { | |||
List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree()); | |||
for (DiffEntry diff : diffEntries) { | |||
PathChangeModel pcm = PathChangeModel.from(diff, endCommit.getName()); | |||
PathChangeModel pcm = PathChangeModel.from(diff, endCommit.getName(), repository); | |||
list.add(pcm); | |||
} | |||
Collections.sort(list); | |||
@@ -1167,22 +1190,55 @@ public class JGitUtils { | |||
private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) { | |||
String name; | |||
long size = 0; | |||
if (StringUtils.isEmpty(basePath)) { | |||
name = tw.getPathString(); | |||
} else { | |||
name = tw.getPathString().substring(basePath.length() + 1); | |||
} | |||
ObjectId objectId = tw.getObjectId(0); | |||
FilestoreModel filestoreItem = null; | |||
try { | |||
if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) { | |||
size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB); | |||
if (isPossibleFilestoreItem(size)) { | |||
filestoreItem = getFilestoreItem(tw.getObjectReader().open(objectId)); | |||
} | |||
} | |||
} catch (Throwable t) { | |||
error(t, null, "failed to retrieve blob size for " + tw.getPathString()); | |||
} | |||
return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(), | |||
return new PathModel(name, tw.getPathString(), filestoreItem, size, tw.getFileMode(0).getBits(), | |||
objectId.getName(), commit.getName()); | |||
} | |||
public static boolean isPossibleFilestoreItem(long size) { | |||
return ( (size >= com.gitblit.Constants.LEN_FILESTORE_META_MIN) | |||
&& (size <= com.gitblit.Constants.LEN_FILESTORE_META_MAX)); | |||
} | |||
/** | |||
* | |||
* @return Representative FilestoreModel if valid, otherwise null | |||
*/ | |||
public static FilestoreModel getFilestoreItem(ObjectLoader obj){ | |||
try { | |||
final byte[] blob = obj.getCachedBytes(com.gitblit.Constants.LEN_FILESTORE_META_MAX); | |||
final String meta = new String(blob, "UTF-8"); | |||
return FilestoreModel.fromMetaString(meta); | |||
} catch (LargeObjectException e) { | |||
//Intentionally failing silent | |||
} catch (Exception e) { | |||
error(e, null, "failed to retrieve filestoreItem " + obj.toString()); | |||
} | |||
return null; | |||
} | |||
/** | |||
* Returns a path model by path string | |||
@@ -1197,29 +1253,34 @@ public class JGitUtils { | |||
throws IOException { | |||
long size = 0; | |||
FilestoreModel filestoreItem = null; | |||
TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree()); | |||
String pathString = path; | |||
if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) { | |||
size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB); | |||
pathString = PathUtils.getLastPathComponent(pathString); | |||
} else if (tw.isSubtree()) { | |||
if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) { | |||
// do not display dirs that are behind in the path | |||
if (!Strings.isNullOrEmpty(filter)) { | |||
pathString = path.replaceFirst(filter + "/", ""); | |||
} | |||
pathString = PathUtils.getLastPathComponent(pathString); | |||
size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB); | |||
if (isPossibleFilestoreItem(size)) { | |||
filestoreItem = getFilestoreItem(tw.getObjectReader().open(tw.getObjectId(0))); | |||
} | |||
} else if (tw.isSubtree()) { | |||
// remove the last slash from path in displayed link | |||
if (pathString != null && pathString.charAt(pathString.length()-1) == '/') { | |||
pathString = pathString.substring(0, pathString.length()-1); | |||
} | |||
// do not display dirs that are behind in the path | |||
if (!Strings.isNullOrEmpty(filter)) { | |||
pathString = path.replaceFirst(filter + "/", ""); | |||
} | |||
return new PathModel(pathString, tw.getPathString(), size, tw.getFileMode(0).getBits(), | |||
tw.getObjectId(0).getName(), commit.getName()); | |||
// remove the last slash from path in displayed link | |||
if (pathString != null && pathString.charAt(pathString.length()-1) == '/') { | |||
pathString = pathString.substring(0, pathString.length()-1); | |||
} | |||
} | |||
return new PathModel(pathString, tw.getPathString(), filestoreItem, size, tw.getFileMode(0).getBits(), | |||
tw.getObjectId(0).getName(), commit.getName()); | |||
} | |||
@@ -2513,4 +2574,27 @@ public class JGitUtils { | |||
} | |||
return new MergeResult(MergeStatus.FAILED, null); | |||
} | |||
/** | |||
* Returns the LFS URL for the given oid | |||
* Currently assumes that the Gitblit Filestore is used | |||
* | |||
* @param baseURL | |||
* @param repository name | |||
* @param oid of lfs item | |||
* @return the lfs item URL | |||
*/ | |||
public static String getLfsRepositoryUrl(String baseURL, String repositoryName, String oid) { | |||
if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { | |||
baseURL = baseURL.substring(0, baseURL.length() - 1); | |||
} | |||
return baseURL + com.gitblit.Constants.R_PATH | |||
+ repositoryName + "/" | |||
+ com.gitblit.Constants.R_LFS | |||
+ "objects/" + oid; | |||
} | |||
} |
@@ -32,6 +32,7 @@ import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.behavior.SimpleAttributeModifier; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.link.BookmarkablePageLink; | |||
import org.apache.wicket.markup.html.link.ExternalLink; | |||
import org.apache.wicket.markup.repeater.Item; | |||
import org.apache.wicket.markup.repeater.data.DataView; | |||
import org.apache.wicket.markup.repeater.data.ListDataProvider; | |||
@@ -93,9 +94,34 @@ public class BlamePage extends RepositoryPage { | |||
final BlameType activeBlameType = BlameType.get(blameTypeParam); | |||
RevCommit commit = getCommit(); | |||
add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, objectId, blobPath))); | |||
PathModel pathModel = null; | |||
List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit); | |||
for (PathModel path : paths) { | |||
if (path.path.equals(blobPath)) { | |||
pathModel = path; | |||
break; | |||
} | |||
} | |||
if (pathModel == null) { | |||
final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}", | |||
blobPath, repositoryName, objectId); | |||
logger.error(notFound); | |||
add(new Label("annotation").setVisible(false)); | |||
add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false)); | |||
return; | |||
} | |||
if (pathModel.isFilestoreItem()) { | |||
String rawUrl = JGitUtils.getLfsRepositoryUrl(getContextUrl(), repositoryName, pathModel.getFilestoreOid()); | |||
add(new ExternalLink("blobLink", rawUrl)); | |||
} else { | |||
add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, objectId, blobPath))); | |||
} | |||
add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class, | |||
WicketUtils.newObjectParameter(repositoryName, objectId))); | |||
add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class, | |||
@@ -134,23 +160,9 @@ public class BlamePage extends RepositoryPage { | |||
final DateFormat df = new SimpleDateFormat(format); | |||
df.setTimeZone(getTimeZone()); | |||
PathModel pathModel = null; | |||
List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit); | |||
for (PathModel path : paths) { | |||
if (path.path.equals(blobPath)) { | |||
pathModel = path; | |||
break; | |||
} | |||
} | |||
if (pathModel == null) { | |||
final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}", | |||
blobPath, repositoryName, objectId); | |||
logger.error(notFound); | |||
add(new Label("annotation").setVisible(false)); | |||
add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false)); | |||
return; | |||
} | |||
add(new Label("missingBlob").setVisible(false)); | |||
@@ -45,6 +45,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 wicket:id="filestore" style="margin-right:20px;" class="aui-lozenge aui-lozenge-moved"></span> | |||
<span class="hidden-tablet" style="padding-right:20px;" wicket:id="diffStat"></span> | |||
<span class="link"> | |||
<span class="hidden-tablet"><a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | </span><a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a><span class="hidden-tablet"> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a></span> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> |
@@ -135,6 +135,8 @@ public class CommitDiffPage extends RepositoryPage { | |||
@Override | |||
public void populateItem(final Item<PathChangeModel> item) { | |||
final PathChangeModel entry = item.getModelObject(); | |||
final String filestoreItemUrl = entry.isFilestoreItem() ? JGitUtils.getLfsRepositoryUrl(getContextUrl(), repositoryName, entry.getFilestoreOid()) : null; | |||
Label changeType = new Label("changeType", ""); | |||
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType); | |||
setChangeTypeTooltip(changeType, entry.changeType); | |||
@@ -143,6 +145,7 @@ public class CommitDiffPage extends RepositoryPage { | |||
boolean hasSubmodule = false; | |||
String submodulePath = null; | |||
if (entry.isTree()) { | |||
// tree | |||
item.add(new LinkPanel("pathName", null, entry.path, TreePage.class, | |||
@@ -159,11 +162,13 @@ public class CommitDiffPage extends RepositoryPage { | |||
item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId)); | |||
} else { | |||
// add relative link | |||
item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId)); | |||
item.add(new LinkPanel("pathName", "list", entry.path, entry.isFilestoreItem() ? filestoreItemUrl : "#n" + entry.objectId)); | |||
} | |||
// quick links | |||
if (entry.isSubmodule()) { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
item.add(new ExternalLink("raw", "").setEnabled(false)); | |||
// submodule | |||
item.add(new ExternalLink("patch", "").setEnabled(false)); | |||
@@ -179,12 +184,24 @@ public class CommitDiffPage extends RepositoryPage { | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.ADD) | |||
&& !entry.changeType.equals(ChangeType.DELETE))); | |||
item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path); | |||
item.add(new ExternalLink("raw", rawUrl) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
if (entry.isFilestoreItem()) { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(true)); | |||
item.add(new ExternalLink("view", filestoreItemUrl)); | |||
item.add(new ExternalLink("raw", filestoreItemUrl)); | |||
} else { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
item.add(new ExternalLink("raw", RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
} | |||
item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.ADD) |
@@ -77,8 +77,9 @@ | |||
<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="path"><span wicket:id="pathName">[commit path]</span></td> | |||
<td class="hidden-phone rightAlign"> | |||
<span wicket:id="filestore" style="margin-right:20px;" class="aui-lozenge aui-lozenge-moved"></span> | |||
<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> | <span class="hidden-tablet"><a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a> | </span><a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> |
@@ -163,6 +163,8 @@ public class CommitPage extends RepositoryPage { | |||
@Override | |||
public void populateItem(final Item<PathChangeModel> item) { | |||
final PathChangeModel entry = item.getModelObject(); | |||
final String filestoreItemUrl = entry.isFilestoreItem() ? JGitUtils.getLfsRepositoryUrl(getContextUrl(), repositoryName, entry.getFilestoreOid()) : null; | |||
Label changeType = new Label("changeType", ""); | |||
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType); | |||
setChangeTypeTooltip(changeType, entry.changeType); | |||
@@ -194,9 +196,13 @@ public class CommitPage extends RepositoryPage { | |||
path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path); | |||
displayPath = entry.path + " -> " + path; | |||
} | |||
item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class, | |||
WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, path))); | |||
if (entry.isFilestoreItem()) { | |||
item.add(new LinkPanel("pathName", "list", entry.path, filestoreItemUrl)); | |||
} else { | |||
item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, entry.commitId, path))); | |||
} | |||
} | |||
@@ -204,6 +210,8 @@ public class CommitPage extends RepositoryPage { | |||
if (entry.isSubmodule()) { | |||
item.add(new ExternalLink("raw", "").setEnabled(false)); | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
// submodule | |||
item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
@@ -220,12 +228,22 @@ public class CommitPage extends RepositoryPage { | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.ADD) | |||
&& !entry.changeType.equals(ChangeType.DELETE))); | |||
item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path); | |||
item.add(new ExternalLink("raw", rawUrl) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
if (entry.isFilestoreItem()) { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(true)); | |||
item.add(new ExternalLink("view", filestoreItemUrl)); | |||
item.add(new ExternalLink("raw", filestoreItemUrl)); | |||
} else { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path); | |||
item.add(new ExternalLink("raw", rawUrl) | |||
.setEnabled(!entry.changeType.equals(ChangeType.DELETE))); | |||
} | |||
item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils | |||
.newPathParameter(repositoryName, entry.commitId, entry.path)) | |||
.setEnabled(!entry.changeType.equals(ChangeType.ADD) |
@@ -35,7 +35,6 @@ import com.gitblit.models.UserModel; | |||
import com.gitblit.wicket.CacheControl; | |||
import com.gitblit.wicket.FilestoreUI; | |||
import com.gitblit.wicket.GitBlitWebSession; | |||
import com.gitblit.wicket.RequiresAdminRole; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.CacheControl.LastModified; | |||
@@ -22,7 +22,8 @@ | |||
<table style="width:100%" class="pretty"> | |||
<tr wicket:id="changedPath"> | |||
<td class="hidden-phone icon"><img wicket:id="pathIcon" /></td> | |||
<td><span wicket:id="pathName"></span></td> | |||
<td><span wicket:id="pathName"></span></td> | |||
<td class="hidden-phone filestore"><span wicket:id="filestore" class="aui-lozenge aui-lozenge-moved"></span></td> | |||
<td class="hidden-phone size"><span wicket:id="pathSize">[path size]</span></td> | |||
<td class="hidden-phone mode"><span wicket:id="pathPermissions">[path permissions]</span></td> | |||
<td class="treeLinks"><span wicket:id="pathLinks">[path links]</span></td> |
@@ -70,12 +70,13 @@ public class TreePage extends RepositoryPage { | |||
if (path.lastIndexOf('/') > -1) { | |||
parentPath = path.substring(0, path.lastIndexOf('/')); | |||
} | |||
PathModel model = new PathModel("..", parentPath, 0, FileMode.TREE.getBits(), null, objectId); | |||
PathModel model = new PathModel("..", parentPath, null, 0, FileMode.TREE.getBits(), null, objectId); | |||
model.isParentPath = true; | |||
paths.add(0, model); | |||
} | |||
final String id = getBestCommitId(commit); | |||
final ByteFormat byteFormat = new ByteFormat(); | |||
final String baseUrl = WicketUtils.getGitblitURL(getRequest()); | |||
@@ -88,7 +89,9 @@ public class TreePage extends RepositoryPage { | |||
@Override | |||
public void populateItem(final Item<PathModel> item) { | |||
PathModel entry = item.getModelObject(); | |||
item.add(new Label("pathPermissions", JGitUtils.getPermissionsFromMode(entry.mode))); | |||
if (entry.isParentPath) { | |||
// parent .. path | |||
item.add(WicketUtils.newBlankImage("pathIcon")); | |||
@@ -96,6 +99,7 @@ public class TreePage extends RepositoryPage { | |||
item.add(new LinkPanel("pathName", null, entry.name, TreePage.class, | |||
WicketUtils | |||
.newPathParameter(repositoryName, id, entry.path))); | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
item.add(new Label("pathLinks", "")); | |||
} else { | |||
if (entry.isTree()) { | |||
@@ -106,6 +110,8 @@ public class TreePage extends RepositoryPage { | |||
WicketUtils.newPathParameter(repositoryName, id, | |||
entry.path))); | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
// links | |||
Fragment links = new Fragment("pathLinks", "treeLinks", this); | |||
links.add(new BookmarkablePageLink<Void>("tree", TreePage.class, | |||
@@ -133,6 +139,8 @@ public class TreePage extends RepositoryPage { | |||
getShortObjectId(submoduleId), TreePage.class, | |||
WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule)); | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
Fragment links = new Fragment("pathLinks", "submoduleLinks", this); | |||
links.add(new BookmarkablePageLink<Void>("view", SummaryPage.class, | |||
WicketUtils.newRepositoryParameter(submodulePath)).setEnabled(hasSubmodule)); | |||
@@ -155,17 +163,33 @@ public class TreePage extends RepositoryPage { | |||
} | |||
item.add(WicketUtils.getFileImage("pathIcon", entry.name)); | |||
item.add(new Label("pathSize", byteFormat.format(entry.size))); | |||
item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, id, | |||
path))); | |||
// links | |||
Fragment links = new Fragment("pathLinks", "blobLinks", this); | |||
links.add(new BookmarkablePageLink<Void>("view", BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, id, | |||
path))); | |||
String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, id, path); | |||
links.add(new ExternalLink("raw", rawUrl)); | |||
if (entry.isFilestoreItem()) { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(true)); | |||
final String filestoreItemUrl = JGitUtils.getLfsRepositoryUrl(getContextUrl(), repositoryName, entry.getFilestoreOid()); | |||
item.add(new LinkPanel("pathName", "list", displayPath, filestoreItemUrl)); | |||
links.add(new ExternalLink("view", filestoreItemUrl)); | |||
links.add(new ExternalLink("raw", filestoreItemUrl)); | |||
} else { | |||
item.add(new Label("filestore", getString("gb.filestore")).setVisible(false)); | |||
item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, id, | |||
path))); | |||
links.add(new BookmarkablePageLink<Void>("view", BlobPage.class, | |||
WicketUtils.newPathParameter(repositoryName, id, | |||
path))); | |||
String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, id, path); | |||
links.add(new ExternalLink("raw", rawUrl)); | |||
} | |||
links.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, | |||
WicketUtils.newPathParameter(repositoryName, id, | |||
path))); |
@@ -109,7 +109,7 @@ public class HistoryPanel extends BasePanel { | |||
tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path))); | |||
while (tw.next()) { | |||
if (tw.getPathString().equals(path)) { | |||
matchingPath = new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw | |||
matchingPath = new PathChangeModel(tw.getPathString(), tw.getPathString(), null, 0, tw | |||
.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(), | |||
ChangeType.MODIFY); | |||
} |
@@ -1944,6 +1944,12 @@ td.mode { | |||
padding-right:15px; | |||
} | |||
td.filestore { | |||
text-align: right; | |||
width:1em; | |||
padding-right:15px; | |||
} | |||
td.size { | |||
text-align: right; | |||
width: 8em; |
@@ -585,16 +585,16 @@ public class JGitUtilsTest extends GitblitUnitTest { | |||
@Test | |||
public void testZip() throws Exception { | |||
assertFalse(CompressionUtils.zip(null, null, null, null)); | |||
assertFalse(CompressionUtils.zip(null, null, null, null, null)); | |||
Repository repository = GitBlitSuite.getHelloworldRepository(); | |||
File zipFileA = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip"); | |||
FileOutputStream fosA = new FileOutputStream(zipFileA); | |||
boolean successA = CompressionUtils.zip(repository, null, Constants.HEAD, fosA); | |||
boolean successA = CompressionUtils.zip(repository, null, null, Constants.HEAD, fosA); | |||
fosA.close(); | |||
File zipFileB = new File(GitBlitSuite.REPOSITORIES, "helloworld-java.zip"); | |||
FileOutputStream fosB = new FileOutputStream(zipFileB); | |||
boolean successB = CompressionUtils.zip(repository, "java.java", Constants.HEAD, fosB); | |||
boolean successB = CompressionUtils.zip(repository, null, "java.java", Constants.HEAD, fosB); | |||
fosB.close(); | |||
repository.close(); |