mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -35,8 +35,10 @@ package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Strings;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.javahg.HgFileviewCommand;
|
||||
|
||||
@@ -45,6 +47,7 @@ import java.io.IOException;
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Utilizes the mercurial fileview extension in order to support mercurial repository browsing.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@@ -94,16 +97,7 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand
|
||||
cmd.disableSubRepositoryDetection();
|
||||
}
|
||||
|
||||
BrowserResult result = new BrowserResult();
|
||||
|
||||
result.setFiles(cmd.execute());
|
||||
|
||||
if (!Strings.isNullOrEmpty(request.getRevision())) {
|
||||
result.setRevision(request.getRevision());
|
||||
} else {
|
||||
result.setRevision("tip");
|
||||
}
|
||||
|
||||
return result;
|
||||
FileObject file = cmd.execute();
|
||||
return new BrowserResult(MoreObjects.firstNonNull(request.getRevision(), "tip"), file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,35 +50,31 @@ import sonia.scm.repository.SubRepository;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Mercurial command to list files of a repository.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgFileviewCommand extends AbstractCommand
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param repository
|
||||
*/
|
||||
public HgFileviewCommand(Repository repository)
|
||||
private boolean disableLastCommit = false;
|
||||
|
||||
private HgFileviewCommand(Repository repository)
|
||||
{
|
||||
super(repository);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Create command for the given repository.
|
||||
*
|
||||
* @param repository repository
|
||||
*
|
||||
* @param repository
|
||||
*
|
||||
* @return
|
||||
* @return fileview command
|
||||
*/
|
||||
public static HgFileviewCommand on(Repository repository)
|
||||
{
|
||||
@@ -86,13 +82,11 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Disable last commit fetching for file objects.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @return {@code this}
|
||||
*/
|
||||
public HgFileviewCommand disableLastCommit()
|
||||
{
|
||||
public HgFileviewCommand disableLastCommit() {
|
||||
disableLastCommit = true;
|
||||
cmdAppend("-d");
|
||||
|
||||
@@ -100,132 +94,128 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Disables sub repository detection
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @return {@code this}
|
||||
*/
|
||||
public HgFileviewCommand disableSubRepositoryDetection()
|
||||
{
|
||||
public HgFileviewCommand disableSubRepositoryDetection() {
|
||||
cmdAppend("-s");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Start file object fetching at the given path.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @param path path to start fetching
|
||||
*
|
||||
* @throws IOException
|
||||
* @return {@code this}
|
||||
*/
|
||||
public List<FileObject> execute() throws IOException
|
||||
{
|
||||
cmdAppend("-t");
|
||||
|
||||
List<FileObject> files = Lists.newArrayList();
|
||||
|
||||
HgInputStream stream = launchStream();
|
||||
|
||||
while (stream.peek() != -1)
|
||||
{
|
||||
FileObject file = null;
|
||||
char type = (char) stream.read();
|
||||
|
||||
if (type == 'd')
|
||||
{
|
||||
file = readDirectory(stream);
|
||||
}
|
||||
else if (type == 'f')
|
||||
{
|
||||
file = readFile(stream);
|
||||
}
|
||||
else if (type == 's')
|
||||
{
|
||||
file = readSubRepository(stream);
|
||||
}
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param path
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public HgFileviewCommand path(String path)
|
||||
{
|
||||
public HgFileviewCommand path(String path) {
|
||||
cmdAppend("-p", path);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Fetch file objects recursive.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @return {@code this}
|
||||
*/
|
||||
public HgFileviewCommand recursive()
|
||||
{
|
||||
public HgFileviewCommand recursive() {
|
||||
cmdAppend("-c");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Use given revision for file view.
|
||||
*
|
||||
* @param revision revision id, hash, tag or branch
|
||||
*
|
||||
* @param revision
|
||||
*
|
||||
* @return
|
||||
* @return {@code this}
|
||||
*/
|
||||
public HgFileviewCommand rev(String revision)
|
||||
{
|
||||
public HgFileviewCommand rev(String revision) {
|
||||
cmdAppend("-r", revision);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Executes the mercurial command and parses the output.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return HgFileviewExtension.NAME;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param stream
|
||||
*
|
||||
* @return
|
||||
* @return file object
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private FileObject readDirectory(HgInputStream stream) throws IOException
|
||||
public FileObject execute() throws IOException
|
||||
{
|
||||
cmdAppend("-t");
|
||||
|
||||
Deque<FileObject> stack = new LinkedList<>();
|
||||
|
||||
HgInputStream stream = launchStream();
|
||||
|
||||
FileObject last = null;
|
||||
while (stream.peek() != -1) {
|
||||
FileObject file = read(stream);
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
FileObject current = stack.peek();
|
||||
if (isParent(current, file)) {
|
||||
current.addChild(file);
|
||||
break;
|
||||
} else {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
stack.push(file);
|
||||
}
|
||||
last = file;
|
||||
}
|
||||
|
||||
if (stack.isEmpty()) {
|
||||
// if the stack is empty, the requested path is probably a file
|
||||
return last;
|
||||
} else {
|
||||
// if the stack is not empty, the requested path is a directory
|
||||
return stack.getLast();
|
||||
}
|
||||
}
|
||||
|
||||
private FileObject read(HgInputStream stream) throws IOException {
|
||||
char type = (char) stream.read();
|
||||
|
||||
FileObject file;
|
||||
switch (type) {
|
||||
case 'd':
|
||||
file = readDirectory(stream);
|
||||
break;
|
||||
case 'f':
|
||||
file = readFile(stream);
|
||||
break;
|
||||
case 's':
|
||||
file = readSubRepository(stream);
|
||||
break;
|
||||
default:
|
||||
throw new IOException("unknown file object type: " + type);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
private boolean isParent(FileObject parent, FileObject child) {
|
||||
String parentPath = parent.getPath();
|
||||
if (parentPath.equals("")) {
|
||||
return true;
|
||||
}
|
||||
return child.getParentPath().equals(parentPath);
|
||||
}
|
||||
|
||||
private FileObject readDirectory(HgInputStream stream) throws IOException {
|
||||
FileObject directory = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\0'));
|
||||
|
||||
@@ -236,18 +226,7 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param stream
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private FileObject readFile(HgInputStream stream) throws IOException
|
||||
{
|
||||
private FileObject readFile(HgInputStream stream) throws IOException {
|
||||
FileObject file = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\n'));
|
||||
|
||||
@@ -259,8 +238,7 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
DateTime timestamp = stream.dateTimeUpTo(' ');
|
||||
String description = stream.textUpTo('\0');
|
||||
|
||||
if (!disableLastCommit)
|
||||
{
|
||||
if (!disableLastCommit) {
|
||||
file.setLastModified(timestamp.getDate().getTime());
|
||||
file.setDescription(description);
|
||||
}
|
||||
@@ -268,18 +246,7 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param stream
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private FileObject readSubRepository(HgInputStream stream) throws IOException
|
||||
{
|
||||
private FileObject readSubRepository(HgInputStream stream) throws IOException {
|
||||
FileObject directory = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\n'));
|
||||
|
||||
@@ -292,8 +259,7 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
|
||||
SubRepository subRepository = new SubRepository(url);
|
||||
|
||||
if (!Strings.isNullOrEmpty(revision))
|
||||
{
|
||||
if (!Strings.isNullOrEmpty(revision)) {
|
||||
subRepository.setRevision(revision);
|
||||
}
|
||||
|
||||
@@ -302,48 +268,33 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param path
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String removeTrailingSlash(String path)
|
||||
{
|
||||
if (path.endsWith("/"))
|
||||
{
|
||||
private String removeTrailingSlash(String path) {
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param path
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getNameFromPath(String path)
|
||||
{
|
||||
private String getNameFromPath(String path) {
|
||||
int index = path.lastIndexOf('/');
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
if (index > 0) {
|
||||
path = path.substring(index + 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
/**
|
||||
* Returns the name of the mercurial command.
|
||||
*
|
||||
* @return command name
|
||||
*/
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return HgFileviewExtension.NAME;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
private boolean disableLastCommit = false;
|
||||
}
|
||||
|
||||
@@ -2,26 +2,28 @@
|
||||
import React from "react";
|
||||
import { repositories } from "@scm-manager/ui-components";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
repository: Repository,
|
||||
t: string => string
|
||||
}
|
||||
|
||||
class ProtocolInformation extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
const { repository, t } = this.props;
|
||||
const href = repositories.getProtocolLinkByType(repository, "http");
|
||||
if (!href) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h4>Clone the repository</h4>
|
||||
<h4>{t("scm-hg-plugin.information.clone")}</h4>
|
||||
<pre>
|
||||
<code>hg clone {href}</code>
|
||||
</pre>
|
||||
<h4>Create a new repository</h4>
|
||||
<h4>{t("scm-hg-plugin.information.create")}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
hg init {repository.name}
|
||||
@@ -41,7 +43,7 @@ class ProtocolInformation extends React.Component<Props> {
|
||||
<br />
|
||||
</code>
|
||||
</pre>
|
||||
<h4>Push an existing repository</h4>
|
||||
<h4>{t("scm-hg-plugin.information.replace")}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
# add the repository url as default to your .hg/hgrc e.g:
|
||||
@@ -59,4 +61,4 @@ class ProtocolInformation extends React.Component<Props> {
|
||||
|
||||
}
|
||||
|
||||
export default ProtocolInformation;
|
||||
export default translate("plugins")(ProtocolInformation);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"scm-hg-plugin": {
|
||||
"information": {
|
||||
"clone" : "Repository Klonen",
|
||||
"create" : "Neue Repository erstellen",
|
||||
"replace" : "Eine existierende Repository aktualisieren"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"scm-hg-plugin": {
|
||||
"information": {
|
||||
"clone" : "Clone the repository",
|
||||
"create" : "Create a new repository",
|
||||
"replace" : "Push an existing repository"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,61 +32,129 @@
|
||||
|
||||
Prints date, size and last message of files.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from mercurial import cmdutil,util
|
||||
|
||||
cmdtable = {}
|
||||
command = cmdutil.command(cmdtable)
|
||||
|
||||
FILE_MARKER = '<files>'
|
||||
|
||||
class File_Collector:
|
||||
|
||||
def __init__(self, recursive = False):
|
||||
self.recursive = recursive
|
||||
self.structure = defaultdict(dict, ((FILE_MARKER, []),))
|
||||
|
||||
def collect(self, paths, path = "", dir_only = False):
|
||||
for p in paths:
|
||||
if p.startswith(path):
|
||||
self.attach(self.extract_name_without_parent(path, p), self.structure, dir_only)
|
||||
|
||||
def attach(self, branch, trunk, dir_only = False):
|
||||
parts = branch.split('/', 1)
|
||||
if len(parts) == 1: # branch is a file
|
||||
if dir_only:
|
||||
trunk[parts[0]] = defaultdict(dict, ((FILE_MARKER, []),))
|
||||
else:
|
||||
trunk[FILE_MARKER].append(parts[0])
|
||||
else:
|
||||
node, others = parts
|
||||
if node not in trunk:
|
||||
trunk[node] = defaultdict(dict, ((FILE_MARKER, []),))
|
||||
if self.recursive:
|
||||
self.attach(others, trunk[node], dir_only)
|
||||
|
||||
def extract_name_without_parent(self, parent, name_with_parent):
|
||||
if len(parent) > 0:
|
||||
name_without_parent = name_with_parent[len(parent):]
|
||||
if name_without_parent.startswith("/"):
|
||||
name_without_parent = name_without_parent[1:]
|
||||
return name_without_parent
|
||||
return name_with_parent
|
||||
|
||||
class File_Object:
|
||||
def __init__(self, directory, path):
|
||||
self.directory = directory
|
||||
self.path = path
|
||||
self.children = []
|
||||
self.sub_repository = None
|
||||
|
||||
def get_name(self):
|
||||
parts = self.path.split("/")
|
||||
return parts[len(parts) - 1]
|
||||
|
||||
def get_parent(self):
|
||||
idx = self.path.rfind("/")
|
||||
if idx > 0:
|
||||
return self.path[0:idx]
|
||||
return ""
|
||||
|
||||
def add_child(self, child):
|
||||
self.children.append(child)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.children[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.children)
|
||||
|
||||
def __repr__(self):
|
||||
result = self.path
|
||||
if self.directory:
|
||||
result += "/"
|
||||
return result
|
||||
|
||||
class File_Walker:
|
||||
|
||||
def __init__(self, sub_repositories, visitor):
|
||||
self.visitor = visitor
|
||||
self.sub_repositories = sub_repositories
|
||||
|
||||
def create_file(self, path):
|
||||
return File_Object(False, path)
|
||||
|
||||
def create_directory(self, path):
|
||||
directory = File_Object(True, path)
|
||||
if path in self.sub_repositories:
|
||||
directory.sub_repository = self.sub_repositories[path]
|
||||
return directory
|
||||
|
||||
def visit_file(self, path):
|
||||
file = self.create_file(path)
|
||||
self.visit(file)
|
||||
|
||||
def visit_directory(self, path):
|
||||
file = self.create_directory(path)
|
||||
self.visit(file)
|
||||
|
||||
def visit(self, file):
|
||||
self.visitor.visit(file)
|
||||
|
||||
def create_path(self, parent, path):
|
||||
if len(parent) > 0:
|
||||
return parent + "/" + path
|
||||
return path
|
||||
|
||||
def walk(self, structure, parent = ""):
|
||||
for key, value in structure.iteritems():
|
||||
if key == FILE_MARKER:
|
||||
if value:
|
||||
for v in value:
|
||||
self.visit_file(self.create_path(parent, v))
|
||||
else:
|
||||
self.visit_directory(self.create_path(parent, key))
|
||||
if isinstance(value, dict):
|
||||
self.walk(value, self.create_path(parent, key))
|
||||
else:
|
||||
self.visit_directory(self.create_path(parent, value))
|
||||
|
||||
class SubRepository:
|
||||
url = None
|
||||
revision = None
|
||||
|
||||
def removeTrailingSlash(path):
|
||||
if path.endswith('/'):
|
||||
path = path[0:-1]
|
||||
return path
|
||||
|
||||
def appendTrailingSlash(path):
|
||||
if not path.endswith('/'):
|
||||
path += '/'
|
||||
return path
|
||||
|
||||
def collectFiles(revCtx, path, files, directories, recursive):
|
||||
length = 0
|
||||
paths = []
|
||||
mf = revCtx.manifest()
|
||||
if path is "":
|
||||
length = 1
|
||||
for f in mf:
|
||||
paths.append(f)
|
||||
else:
|
||||
length = len(path.split('/')) + 1
|
||||
directory = path
|
||||
if not directory.endswith('/'):
|
||||
directory += '/'
|
||||
|
||||
for f in mf:
|
||||
if f.startswith(directory):
|
||||
paths.append(f)
|
||||
|
||||
if not recursive:
|
||||
for p in paths:
|
||||
parts = p.split('/')
|
||||
depth = len(parts)
|
||||
if depth is length:
|
||||
file = revCtx[p]
|
||||
files.append(file)
|
||||
elif depth > length:
|
||||
dirpath = ''
|
||||
for i in range(0, length):
|
||||
dirpath += parts[i] + '/'
|
||||
if not dirpath in directories:
|
||||
directories.append(dirpath)
|
||||
else:
|
||||
for p in paths:
|
||||
files.append(revCtx[p])
|
||||
|
||||
def createSubRepositoryMap(revCtx):
|
||||
def collect_sub_repositories(revCtx):
|
||||
subrepos = {}
|
||||
try:
|
||||
hgsub = revCtx.filectx('.hgsub').data().split('\n')
|
||||
@@ -98,7 +166,7 @@ def createSubRepositoryMap(revCtx):
|
||||
subrepos[parts[0].strip()] = subrepo
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n')
|
||||
for line in hgsubstate:
|
||||
@@ -109,32 +177,77 @@ def createSubRepositoryMap(revCtx):
|
||||
subrepo.revision = subrev
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
return subrepos
|
||||
|
||||
def printSubRepository(ui, path, subrepository, transport):
|
||||
format = '%s %s %s\n'
|
||||
if transport:
|
||||
format = 's%s\n%s %s\0'
|
||||
ui.write( format % (appendTrailingSlash(path), subrepository.revision, subrepository.url))
|
||||
|
||||
def printDirectory(ui, path, transport):
|
||||
format = '%s\n'
|
||||
if transport:
|
||||
format = 'd%s\0'
|
||||
ui.write( format % path)
|
||||
|
||||
def printFile(ui, repo, file, disableLastCommit, transport):
|
||||
date = '0 0'
|
||||
description = 'n/a'
|
||||
if not disableLastCommit:
|
||||
linkrev = repo[file.linkrev()]
|
||||
date = '%d %d' % util.parsedate(linkrev.date())
|
||||
description = linkrev.description()
|
||||
format = '%s %i %s %s\n'
|
||||
if transport:
|
||||
format = 'f%s\n%i %s %s\0'
|
||||
ui.write( format % (file.path(), file.size(), date, description) )
|
||||
|
||||
class File_Printer:
|
||||
|
||||
def __init__(self, ui, repo, revCtx, disableLastCommit, transport):
|
||||
self.ui = ui
|
||||
self.repo = repo
|
||||
self.revCtx = revCtx
|
||||
self.disableLastCommit = disableLastCommit
|
||||
self.transport = transport
|
||||
|
||||
def print_directory(self, path):
|
||||
format = '%s/\n'
|
||||
if self.transport:
|
||||
format = 'd%s/\0'
|
||||
self.ui.write( format % path)
|
||||
|
||||
def print_file(self, path):
|
||||
file = self.revCtx[path]
|
||||
date = '0 0'
|
||||
description = 'n/a'
|
||||
if not self.disableLastCommit:
|
||||
linkrev = self.repo[file.linkrev()]
|
||||
date = '%d %d' % util.parsedate(linkrev.date())
|
||||
description = linkrev.description()
|
||||
format = '%s %i %s %s\n'
|
||||
if self.transport:
|
||||
format = 'f%s\n%i %s %s\0'
|
||||
self.ui.write( format % (file.path(), file.size(), date, description) )
|
||||
|
||||
def print_sub_repository(self, path, subrepo):
|
||||
format = '%s/ %s %s\n'
|
||||
if self.transport:
|
||||
format = 's%s/\n%s %s\0'
|
||||
self.ui.write( format % (path, subrepo.revision, subrepo.url))
|
||||
|
||||
def visit(self, file):
|
||||
if file.sub_repository:
|
||||
self.print_sub_repository(file.path, file.sub_repository)
|
||||
elif file.directory:
|
||||
self.print_directory(file.path)
|
||||
else:
|
||||
self.print_file(file.path)
|
||||
|
||||
class File_Viewer:
|
||||
def __init__(self, revCtx, visitor):
|
||||
self.revCtx = revCtx
|
||||
self.visitor = visitor
|
||||
self.sub_repositories = {}
|
||||
self.recursive = False
|
||||
|
||||
def remove_ending_slash(self, path):
|
||||
if path.endswith("/"):
|
||||
return path[:-1]
|
||||
return path
|
||||
|
||||
def view(self, path = ""):
|
||||
manifest = self.revCtx.manifest()
|
||||
if len(path) > 0 and path in manifest:
|
||||
self.visitor.visit(File_Object(False, path))
|
||||
else:
|
||||
p = self.remove_ending_slash(path)
|
||||
|
||||
collector = File_Collector(self.recursive)
|
||||
walker = File_Walker(self.sub_repositories, self.visitor)
|
||||
|
||||
self.visitor.visit(File_Object(True, p))
|
||||
collector.collect(manifest, p)
|
||||
collector.collect(self.sub_repositories.keys(), p, True)
|
||||
walker.walk(collector.structure, p)
|
||||
|
||||
@command('fileview', [
|
||||
('r', 'revision', 'tip', 'revision to print'),
|
||||
@@ -145,23 +258,12 @@ def printFile(ui, repo, file, disableLastCommit, transport):
|
||||
('t', 'transport', False, 'format the output for command server'),
|
||||
])
|
||||
def fileview(ui, repo, **opts):
|
||||
files = []
|
||||
directories = []
|
||||
revision = opts['revision']
|
||||
if revision == None:
|
||||
revision = 'tip'
|
||||
revCtx = repo[revision]
|
||||
path = opts['path']
|
||||
if path.endswith('/'):
|
||||
path = path[0:-1]
|
||||
transport = opts['transport']
|
||||
collectFiles(revCtx, path, files, directories, opts['recursive'])
|
||||
if not opts['disableSubRepositoryDetection']:
|
||||
subRepositories = createSubRepositoryMap(revCtx)
|
||||
for k, v in subRepositories.iteritems():
|
||||
if k.startswith(path):
|
||||
printSubRepository(ui, k, v, transport)
|
||||
for d in directories:
|
||||
printDirectory(ui, d, transport)
|
||||
for f in files:
|
||||
printFile(ui, repo, f, opts['disableLastCommit'], transport)
|
||||
revCtx = repo[opts["revision"]]
|
||||
subrepos = {}
|
||||
if not opts["disableSubRepositoryDetection"]:
|
||||
subrepos = collect_sub_repositories(revCtx)
|
||||
printer = File_Printer(ui, repo, revCtx, opts["disableLastCommit"], opts["transport"])
|
||||
viewer = File_Viewer(revCtx, printer)
|
||||
viewer.recursive = opts["recursive"]
|
||||
viewer.sub_repositories = subrepos
|
||||
viewer.view(opts["path"])
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
from fileview import File_Viewer, SubRepository
|
||||
import unittest
|
||||
|
||||
class DummyRevContext():
|
||||
|
||||
def __init__(self, mf):
|
||||
self.mf = mf
|
||||
|
||||
def manifest(self):
|
||||
return self.mf
|
||||
|
||||
class File_Object_Collector():
|
||||
|
||||
def __init__(self):
|
||||
self.stack = []
|
||||
|
||||
def __getitem__(self, key):
|
||||
if len(self.stack) == 0 and key == 0:
|
||||
return self.last
|
||||
return self.stack[key]
|
||||
|
||||
def visit(self, file):
|
||||
while len(self.stack) > 0:
|
||||
current = self.stack[-1]
|
||||
if file.get_parent() == current.path:
|
||||
current.add_child(file)
|
||||
break
|
||||
else:
|
||||
self.stack.pop()
|
||||
if file.directory:
|
||||
self.stack.append(file)
|
||||
self.last = file
|
||||
|
||||
|
||||
class Test_File_Viewer(unittest.TestCase):
|
||||
|
||||
def test_single_file(self):
|
||||
root = self.collect(["a.txt", "b.txt"], "a.txt")
|
||||
self.assertFile(root, "a.txt")
|
||||
|
||||
def test_simple(self):
|
||||
root = self.collect(["a.txt", "b.txt"])
|
||||
self.assertFile(root[0], "a.txt")
|
||||
self.assertFile(root[1], "b.txt")
|
||||
|
||||
def test_recursive(self):
|
||||
root = self.collect(["a", "b", "c/d.txt", "c/e.txt", "f.txt", "c/g/h.txt"], "", True)
|
||||
self.assertChildren(root, ["a", "b", "f.txt", "c"])
|
||||
c = root[3]
|
||||
self.assertDirectory(c, "c")
|
||||
self.assertChildren(c, ["c/d.txt", "c/e.txt", "c/g"])
|
||||
g = c[2]
|
||||
self.assertDirectory(g, "c/g")
|
||||
self.assertChildren(g, ["c/g/h.txt"])
|
||||
|
||||
def test_recursive_with_path(self):
|
||||
root = self.collect(["a", "b", "c/d.txt", "c/e.txt", "f.txt", "c/g/h.txt"], "c", True)
|
||||
self.assertDirectory(root, "c")
|
||||
self.assertChildren(root, ["c/d.txt", "c/e.txt", "c/g"])
|
||||
g = root[2]
|
||||
self.assertDirectory(g, "c/g")
|
||||
self.assertChildren(g, ["c/g/h.txt"])
|
||||
|
||||
def test_recursive_with_deep_path(self):
|
||||
root = self.collect(["a", "b", "c/d.txt", "c/e.txt", "f.txt", "c/g/h.txt"], "c/g", True)
|
||||
self.assertDirectory(root, "c/g")
|
||||
self.assertChildren(root, ["c/g/h.txt"])
|
||||
|
||||
def test_non_recursive(self):
|
||||
root = self.collect(["a.txt", "b.txt", "c/d.txt", "c/e.txt", "c/f/g.txt"])
|
||||
self.assertDirectory(root, "")
|
||||
self.assertChildren(root, ["a.txt", "b.txt", "c"])
|
||||
c = root[2]
|
||||
self.assertEmptyDirectory(c, "c")
|
||||
|
||||
def test_non_recursive_with_path(self):
|
||||
root = self.collect(["a.txt", "b.txt", "c/d.txt", "c/e.txt", "c/f/g.txt"], "c")
|
||||
self.assertDirectory(root, "c")
|
||||
self.assertChildren(root, ["c/d.txt", "c/e.txt", "c/f"])
|
||||
f = root[2]
|
||||
self.assertEmptyDirectory(f, "c/f")
|
||||
|
||||
def test_non_recursive_with_path_with_ending_slash(self):
|
||||
root = self.collect(["c/d.txt"], "c/")
|
||||
self.assertDirectory(root, "c")
|
||||
self.assertFile(root[0], "c/d.txt")
|
||||
|
||||
def test_with_sub_directory(self):
|
||||
revCtx = DummyRevContext(["a.txt", "b/c.txt"])
|
||||
collector = File_Object_Collector()
|
||||
viewer = File_Viewer(revCtx, collector)
|
||||
sub_repositories = {}
|
||||
sub_repositories["d"] = SubRepository()
|
||||
sub_repositories["d"].url = "d"
|
||||
sub_repositories["d"].revision = "42"
|
||||
viewer.sub_repositories = sub_repositories
|
||||
viewer.view()
|
||||
|
||||
d = collector[0][2]
|
||||
self.assertDirectory(d, "d")
|
||||
|
||||
|
||||
def collect(self, paths, path = "", recursive = False):
|
||||
revCtx = DummyRevContext(paths)
|
||||
collector = File_Object_Collector()
|
||||
|
||||
viewer = File_Viewer(revCtx, collector)
|
||||
viewer.recursive = recursive
|
||||
viewer.view(path)
|
||||
|
||||
return collector[0]
|
||||
|
||||
def assertChildren(self, parent, expectedPaths):
|
||||
self.assertEqual(len(parent), len(expectedPaths))
|
||||
for idx,item in enumerate(parent.children):
|
||||
self.assertEqual(item.path, expectedPaths[idx])
|
||||
|
||||
def assertFile(self, file, expectedPath):
|
||||
self.assertEquals(file.path, expectedPath)
|
||||
self.assertFalse(file.directory)
|
||||
|
||||
def assertDirectory(self, file, expectedPath):
|
||||
self.assertEquals(file.path, expectedPath)
|
||||
self.assertTrue(file.directory)
|
||||
|
||||
def assertEmptyDirectory(self, file, expectedPath):
|
||||
self.assertDirectory(file, expectedPath)
|
||||
self.assertTrue(len(file.children) == 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -33,14 +33,12 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.FileObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -48,18 +46,25 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgBrowseCommandTest extends AbstractHgCommandTestBase
|
||||
{
|
||||
public class HgBrowseCommandTest extends AbstractHgCommandTestBase {
|
||||
|
||||
@Test
|
||||
public void testBrowseWithFilePath() throws IOException {
|
||||
BrowseCommandRequest request = new BrowseCommandRequest();
|
||||
request.setPath("a.txt");
|
||||
FileObject file = new HgBrowseCommand(cmdContext, repository).getBrowserResult(request).getFile();
|
||||
assertEquals("a.txt", file.getName());
|
||||
assertFalse(file.isDirectory());
|
||||
assertTrue(file.getChildren().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrowse() throws IOException {
|
||||
List<FileObject> foList = getRootFromTip(new BrowseCommandRequest());
|
||||
Collection<FileObject> foList = getRootFromTip(new BrowseCommandRequest());
|
||||
FileObject a = getFileObject(foList, "a.txt");
|
||||
FileObject c = getFileObject(foList, "c");
|
||||
|
||||
@@ -85,7 +90,9 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase
|
||||
|
||||
assertNotNull(result);
|
||||
|
||||
List<FileObject> foList = result.getFiles();
|
||||
FileObject c = result.getFile();
|
||||
assertEquals("c", c.getName());
|
||||
Collection<FileObject> foList = c.getChildren();
|
||||
|
||||
assertNotNull(foList);
|
||||
assertFalse(foList.isEmpty());
|
||||
@@ -128,7 +135,7 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase
|
||||
|
||||
request.setDisableLastCommit(true);
|
||||
|
||||
List<FileObject> foList = getRootFromTip(request);
|
||||
Collection<FileObject> foList = getRootFromTip(request);
|
||||
|
||||
FileObject a = getFileObject(foList, "a.txt");
|
||||
|
||||
@@ -147,11 +154,16 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase
|
||||
|
||||
assertNotNull(result);
|
||||
|
||||
List<FileObject> foList = result.getFiles();
|
||||
FileObject root = result.getFile();
|
||||
Collection<FileObject> foList = root.getChildren();
|
||||
|
||||
assertNotNull(foList);
|
||||
assertFalse(foList.isEmpty());
|
||||
assertEquals(5, foList.size());
|
||||
assertEquals(4, foList.size());
|
||||
|
||||
FileObject c = getFileObject(foList, "c");
|
||||
assertTrue(c.isDirectory());
|
||||
assertEquals(2, c.getChildren().size());
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
@@ -165,32 +177,22 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private FileObject getFileObject(List<FileObject> foList, String name)
|
||||
private FileObject getFileObject(Collection<FileObject> foList, String name)
|
||||
{
|
||||
FileObject a = null;
|
||||
|
||||
for (FileObject f : foList)
|
||||
{
|
||||
if (name.equals(f.getName()))
|
||||
{
|
||||
a = f;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(a);
|
||||
|
||||
return a;
|
||||
return foList.stream()
|
||||
.filter(f -> name.equals(f.getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("file " + name + " not found"));
|
||||
}
|
||||
|
||||
private List<FileObject> getRootFromTip(BrowseCommandRequest request) throws IOException {
|
||||
private Collection<FileObject> getRootFromTip(BrowseCommandRequest request) throws IOException {
|
||||
BrowserResult result = new HgBrowseCommand(cmdContext,
|
||||
repository).getBrowserResult(request);
|
||||
|
||||
assertNotNull(result);
|
||||
|
||||
List<FileObject> foList = result.getFiles();
|
||||
FileObject root = result.getFile();
|
||||
Collection<FileObject> foList = root.getChildren();
|
||||
|
||||
assertNotNull(foList);
|
||||
assertFalse(foList.isEmpty());
|
||||
|
||||
Reference in New Issue
Block a user