mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-05 11:50:58 +01:00
Extract parsing of fileview stream and bootstrap tests
This commit is contained in:
@@ -35,23 +35,16 @@ package sonia.scm.repository.spi.javahg;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.aragost.javahg.DateTime;
|
||||
import com.aragost.javahg.Repository;
|
||||
import com.aragost.javahg.internals.AbstractCommand;
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Mercurial command to list files of a repository.
|
||||
*
|
||||
@@ -60,7 +53,6 @@ import java.util.LinkedList;
|
||||
public class HgFileviewCommand extends AbstractCommand
|
||||
{
|
||||
|
||||
public static final char TRUNCATED_MARK = 't';
|
||||
private boolean disableLastCommit = false;
|
||||
|
||||
private HgFileviewCommand(Repository repository)
|
||||
@@ -182,139 +174,9 @@ public class HgFileviewCommand extends AbstractCommand
|
||||
{
|
||||
cmdAppend("-t");
|
||||
|
||||
Deque<FileObject> stack = new LinkedList<>();
|
||||
|
||||
HgInputStream stream = launchStream();
|
||||
|
||||
FileObject last = null;
|
||||
while (stream.peek() != -1 && stream.peek() != TRUNCATED_MARK) {
|
||||
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
|
||||
if (stream.read() == TRUNCATED_MARK) {
|
||||
stack.getLast().setTruncated(true);
|
||||
}
|
||||
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'));
|
||||
|
||||
directory.setName(getNameFromPath(path));
|
||||
directory.setDirectory(true);
|
||||
directory.setPath(path);
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private FileObject readFile(HgInputStream stream) throws IOException {
|
||||
FileObject file = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\n'));
|
||||
|
||||
file.setName(getNameFromPath(path));
|
||||
file.setPath(path);
|
||||
file.setDirectory(false);
|
||||
file.setLength((long) stream.decimalIntUpTo(' '));
|
||||
|
||||
DateTime timestamp = stream.dateTimeUpTo(' ');
|
||||
String description = stream.textUpTo('\0');
|
||||
|
||||
if (!disableLastCommit) {
|
||||
file.setCommitDate(timestamp.getDate().getTime());
|
||||
file.setDescription(description);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private FileObject readSubRepository(HgInputStream stream) throws IOException {
|
||||
FileObject directory = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\n'));
|
||||
|
||||
directory.setName(getNameFromPath(path));
|
||||
directory.setDirectory(true);
|
||||
directory.setPath(path);
|
||||
|
||||
String revision = stream.textUpTo(' ');
|
||||
String url = stream.textUpTo('\0');
|
||||
|
||||
SubRepository subRepository = new SubRepository(url);
|
||||
|
||||
if (!Strings.isNullOrEmpty(revision)) {
|
||||
subRepository.setRevision(revision);
|
||||
}
|
||||
|
||||
directory.setSubRepository(subRepository);
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private String removeTrailingSlash(String path) {
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private String getNameFromPath(String path) {
|
||||
int index = path.lastIndexOf('/');
|
||||
|
||||
if (index > 0) {
|
||||
path = path.substring(index + 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
return new HgFileviewCommandResultReader(stream, disableLastCommit).parseResult();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package sonia.scm.repository.spi.javahg;
|
||||
|
||||
import com.aragost.javahg.DateTime;
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
import com.google.common.base.Strings;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
|
||||
class HgFileviewCommandResultReader {
|
||||
|
||||
private static final char TRUNCATED_MARK = 't';
|
||||
|
||||
private final HgInputStream stream;
|
||||
private final boolean disableLastCommit;
|
||||
|
||||
HgFileviewCommandResultReader(HgInputStream stream, boolean disableLastCommit) {
|
||||
this.stream = stream;
|
||||
this.disableLastCommit = disableLastCommit;
|
||||
}
|
||||
|
||||
FileObject parseResult() throws IOException {
|
||||
Deque<FileObject> stack = new LinkedList<>();
|
||||
|
||||
FileObject last = null;
|
||||
while (stream.peek() != -1 && stream.peek() != TRUNCATED_MARK) {
|
||||
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
|
||||
if (stream.read() == TRUNCATED_MARK) {
|
||||
stack.getLast().setTruncated(true);
|
||||
}
|
||||
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'));
|
||||
|
||||
directory.setName(getNameFromPath(path));
|
||||
directory.setDirectory(true);
|
||||
directory.setPath(path);
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private FileObject readFile(HgInputStream stream) throws IOException {
|
||||
FileObject file = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\n'));
|
||||
|
||||
file.setName(getNameFromPath(path));
|
||||
file.setPath(path);
|
||||
file.setDirectory(false);
|
||||
file.setLength((long) stream.decimalIntUpTo(' '));
|
||||
|
||||
DateTime timestamp = stream.dateTimeUpTo(' ');
|
||||
String description = stream.textUpTo('\0');
|
||||
|
||||
if (!disableLastCommit) {
|
||||
file.setCommitDate(timestamp.getDate().getTime());
|
||||
file.setDescription(description);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private FileObject readSubRepository(HgInputStream stream) throws IOException {
|
||||
FileObject directory = new FileObject();
|
||||
String path = removeTrailingSlash(stream.textUpTo('\n'));
|
||||
|
||||
directory.setName(getNameFromPath(path));
|
||||
directory.setDirectory(true);
|
||||
directory.setPath(path);
|
||||
|
||||
String revision = stream.textUpTo(' ');
|
||||
String url = stream.textUpTo('\0');
|
||||
|
||||
SubRepository subRepository = new SubRepository(url);
|
||||
|
||||
if (!Strings.isNullOrEmpty(revision)) {
|
||||
subRepository.setRevision(revision);
|
||||
}
|
||||
|
||||
directory.setSubRepository(subRepository);
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private String removeTrailingSlash(String path) {
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private String getNameFromPath(String path) {
|
||||
int index = path.lastIndexOf('/');
|
||||
|
||||
if (index > 0) {
|
||||
path = path.substring(index + 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package sonia.scm.repository.spi.javahg;
|
||||
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sonia.scm.repository.FileObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HgFileviewCommandResultReaderTest {
|
||||
|
||||
@Test
|
||||
void shouldParseSimpleAttributes() throws IOException {
|
||||
Instant time1 = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
Instant time2 = time1.minus(1, ChronoUnit.DAYS);
|
||||
|
||||
HgFileviewCommandResultReader reader = new MockInput()
|
||||
.dir("")
|
||||
.dir("dir")
|
||||
.file("a.txt", 10, time1.toEpochMilli(), "file a")
|
||||
.file("b.txt", 100, time2.toEpochMilli(), "file b\nwith some\nmore text")
|
||||
.build();
|
||||
|
||||
FileObject fileObject = reader.parseResult();
|
||||
|
||||
assertThat(fileObject.isDirectory()).isTrue();
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("name")
|
||||
.containsExactly("dir", "a.txt", "b.txt");
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("directory")
|
||||
.containsExactly(true, false, false);
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("length")
|
||||
.containsExactly(OptionalLong.empty(), OptionalLong.of(10L), OptionalLong.of(100L));
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("description")
|
||||
.containsExactly(empty(), of("file a"), of("file b\nwith some\nmore text"));
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("commitDate")
|
||||
.containsExactly(OptionalLong.empty(), OptionalLong.of(time1.toEpochMilli()), OptionalLong.of(time2.toEpochMilli()));
|
||||
assertThat(fileObject.isTruncated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTruncatedFlag() throws IOException {
|
||||
HgFileviewCommandResultReader reader = new MockInput()
|
||||
.dir("")
|
||||
.dir("dir")
|
||||
.file("a.txt")
|
||||
.truncated();
|
||||
|
||||
FileObject fileObject = reader.parseResult();
|
||||
|
||||
assertThat(fileObject.isTruncated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseRecursiveResult() throws IOException {
|
||||
HgFileviewCommandResultReader reader = new MockInput()
|
||||
.dir("")
|
||||
.dir("dir")
|
||||
.dir("dir/more")
|
||||
.file("dir/more/c.txt")
|
||||
.file("dir/a.txt")
|
||||
.file("dir/b.txt")
|
||||
.file("d.txt")
|
||||
.build();
|
||||
|
||||
FileObject fileObject = reader.parseResult();
|
||||
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("name")
|
||||
.containsExactly("dir", "d.txt");
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("directory")
|
||||
.containsExactly(true, false);
|
||||
|
||||
FileObject subDir = fileObject.getChildren().iterator().next();
|
||||
assertThat(subDir.getChildren())
|
||||
.extracting("name")
|
||||
.containsExactly("more", "a.txt", "b.txt");
|
||||
assertThat(subDir.getChildren())
|
||||
.extracting("directory")
|
||||
.containsExactly(true, false, false);
|
||||
|
||||
FileObject subSubDir = subDir.getChildren().iterator().next();
|
||||
assertThat(subSubDir.getChildren())
|
||||
.extracting("name")
|
||||
.containsExactly("c.txt");
|
||||
assertThat(subSubDir.getChildren())
|
||||
.extracting("directory")
|
||||
.containsExactly(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreTimeAndCommentWhenDisabled() throws IOException {
|
||||
HgFileviewCommandResultReader reader = new MockInput()
|
||||
.dir("")
|
||||
.dir("c")
|
||||
.file("a.txt")
|
||||
.build();
|
||||
|
||||
FileObject fileObject = reader.parseResult();
|
||||
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("description")
|
||||
.containsOnly(empty());
|
||||
assertThat(fileObject.getChildren())
|
||||
.extracting("commitDate")
|
||||
.containsOnly(OptionalLong.empty());
|
||||
}
|
||||
|
||||
private HgInputStream createInputStream(String input) {
|
||||
return new HgInputStream(new ByteArrayInputStream(input.getBytes(UTF_8)), UTF_8.newDecoder());
|
||||
}
|
||||
|
||||
private class MockInput {
|
||||
private final StringBuilder stringBuilder = new StringBuilder();
|
||||
private boolean disableLastCommit = false;
|
||||
|
||||
MockInput dir(String name) {
|
||||
stringBuilder
|
||||
.append('d')
|
||||
.append(name)
|
||||
.append('/')
|
||||
.append('\0');
|
||||
return this;
|
||||
}
|
||||
|
||||
MockInput file(String name) {
|
||||
disableLastCommit = true;
|
||||
return file(name, 1024, 0, "n/a");
|
||||
}
|
||||
|
||||
MockInput file(String name, int length, long time, String comment) {
|
||||
stringBuilder
|
||||
.append('f')
|
||||
.append(name)
|
||||
.append('\n')
|
||||
.append(length)
|
||||
.append(' ')
|
||||
.append(time/1000)
|
||||
.append(' ')
|
||||
.append(0)
|
||||
.append(' ')
|
||||
.append(comment)
|
||||
.append('\0');
|
||||
return this;
|
||||
}
|
||||
|
||||
HgFileviewCommandResultReader truncated() {
|
||||
stringBuilder.append("t");
|
||||
return build();
|
||||
}
|
||||
|
||||
HgFileviewCommandResultReader build() {
|
||||
HgInputStream inputStream = createInputStream(stringBuilder.toString());
|
||||
return new HgFileviewCommandResultReader(inputStream, disableLastCommit);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user