Merge branch 'stable-3.6'
* stable-3.6: Prepare 3.6.2-SNAPSHOT builds JGit v3.6.1.201501031845-r Trim author/committer name and email in commit header Rename detection should canonicalize line endings PathMatcher should respect "assumeDirectory" flag Change-Id: Idd48c6d94cf1ab09abc07f70d50890b1b78e1833 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
commit
79dacba922
|
@ -83,7 +83,7 @@ public void testIndexingLargeObject() throws IOException,
|
||||||
+ "B\n" //
|
+ "B\n" //
|
||||||
+ "B\n").getBytes("UTF-8");
|
+ "B\n").getBytes("UTF-8");
|
||||||
SimilarityIndex si = new SimilarityIndex();
|
SimilarityIndex si = new SimilarityIndex();
|
||||||
si.hash(new ByteArrayInputStream(in), in.length);
|
si.hash(new ByteArrayInputStream(in), in.length, false);
|
||||||
assertEquals(2, si.size());
|
assertEquals(2, si.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +103,48 @@ public void testCommonScore_SameFiles() throws TableFullException {
|
||||||
assertEquals(100, dst.score(src, 100));
|
assertEquals(100, dst.score(src, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommonScore_SameFiles_CR_canonicalization()
|
||||||
|
throws TableFullException {
|
||||||
|
String text = "" //
|
||||||
|
+ "A\r\n" //
|
||||||
|
+ "B\r\n" //
|
||||||
|
+ "D\r\n" //
|
||||||
|
+ "B\r\n";
|
||||||
|
SimilarityIndex src = hash(text);
|
||||||
|
SimilarityIndex dst = hash(text.replace("\r", ""));
|
||||||
|
assertEquals(8, src.common(dst));
|
||||||
|
assertEquals(8, dst.common(src));
|
||||||
|
|
||||||
|
assertEquals(100, src.score(dst, 100));
|
||||||
|
assertEquals(100, dst.score(src, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommonScoreLargeObject_SameFiles_CR_canonicalization()
|
||||||
|
throws TableFullException, IOException {
|
||||||
|
String text = "" //
|
||||||
|
+ "A\r\n" //
|
||||||
|
+ "B\r\n" //
|
||||||
|
+ "D\r\n" //
|
||||||
|
+ "B\r\n";
|
||||||
|
SimilarityIndex src = new SimilarityIndex();
|
||||||
|
byte[] bytes1 = text.getBytes("UTF-8");
|
||||||
|
src.hash(new ByteArrayInputStream(bytes1), bytes1.length, true);
|
||||||
|
src.sort();
|
||||||
|
|
||||||
|
SimilarityIndex dst = new SimilarityIndex();
|
||||||
|
byte[] bytes2 = text.replace("\r", "").getBytes("UTF-8");
|
||||||
|
dst.hash(new ByteArrayInputStream(bytes2), bytes2.length, true);
|
||||||
|
dst.sort();
|
||||||
|
|
||||||
|
assertEquals(8, src.common(dst));
|
||||||
|
assertEquals(8, dst.common(src));
|
||||||
|
|
||||||
|
assertEquals(100, src.score(dst, 100));
|
||||||
|
assertEquals(100, dst.score(src, 100));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCommonScore_EmptyFiles() throws TableFullException {
|
public void testCommonScore_EmptyFiles() throws TableFullException {
|
||||||
SimilarityIndex src = hash("");
|
SimilarityIndex src = hash("");
|
||||||
|
@ -132,24 +174,8 @@ public void testCommonScore_SimiliarBy75() throws TableFullException {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SimilarityIndex hash(String text) throws TableFullException {
|
private static SimilarityIndex hash(String text) throws TableFullException {
|
||||||
SimilarityIndex src = new SimilarityIndex() {
|
SimilarityIndex src = new SimilarityIndex();
|
||||||
@Override
|
|
||||||
void hash(byte[] raw, int ptr, final int end)
|
|
||||||
throws TableFullException {
|
|
||||||
while (ptr < end) {
|
|
||||||
int hash = raw[ptr] & 0xff;
|
|
||||||
int start = ptr;
|
|
||||||
do {
|
|
||||||
int c = raw[ptr++] & 0xff;
|
|
||||||
if (c == '\n')
|
|
||||||
break;
|
|
||||||
} while (ptr < end && ptr - start < 64);
|
|
||||||
add(hash, ptr - start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
byte[] raw = Constants.encode(text);
|
byte[] raw = Constants.encode(text);
|
||||||
src.setFileSize(raw.length);
|
|
||||||
src.hash(raw, 0, raw.length);
|
src.hash(raw, 0, raw.length);
|
||||||
src.sort();
|
src.sort();
|
||||||
return src;
|
return src;
|
||||||
|
|
|
@ -44,9 +44,12 @@
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assume.assumeFalse;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
|
@ -236,16 +239,62 @@ public void testParentDirectoryGitIgnores() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTrailingSlash() {
|
public void testDirModeAndNoRegex() {
|
||||||
String pattern = "/src/";
|
String pattern = "/src/";
|
||||||
assertMatched(pattern, "/src/");
|
assertMatched(pattern, "/src/");
|
||||||
assertMatched(pattern, "/src/new");
|
assertMatched(pattern, "/src/new");
|
||||||
assertMatched(pattern, "/src/new/a.c");
|
assertMatched(pattern, "/src/new/a.c");
|
||||||
assertMatched(pattern, "/src/a.c");
|
assertMatched(pattern, "/src/a.c");
|
||||||
|
// no match as a "file" pattern, because rule is for directories only
|
||||||
assertNotMatched(pattern, "/src");
|
assertNotMatched(pattern, "/src");
|
||||||
assertNotMatched(pattern, "/srcA/");
|
assertNotMatched(pattern, "/srcA/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDirModeAndRegex1() {
|
||||||
|
// IgnoreRule was buggy for some cases below, therefore using "Assume"
|
||||||
|
Boolean assume = useOldRule;
|
||||||
|
|
||||||
|
String pattern = "a/*/src/";
|
||||||
|
assertMatched(pattern, "a/b/src/");
|
||||||
|
assertMatched(pattern, "a/b/src/new");
|
||||||
|
assertMatched(pattern, "a/b/src/new/a.c");
|
||||||
|
assertMatched(pattern, "a/b/src/a.c");
|
||||||
|
// no match as a "file" pattern, because rule is for directories only
|
||||||
|
assertNotMatched(pattern, "a/b/src", assume);
|
||||||
|
assertNotMatched(pattern, "a/b/srcA/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDirModeAndRegex2() {
|
||||||
|
// IgnoreRule was buggy for some cases below, therefore using "Assume"
|
||||||
|
Boolean assume = useOldRule;
|
||||||
|
|
||||||
|
String pattern = "a/[a-b]/src/";
|
||||||
|
assertMatched(pattern, "a/b/src/");
|
||||||
|
assertMatched(pattern, "a/b/src/new");
|
||||||
|
assertMatched(pattern, "a/b/src/new/a.c");
|
||||||
|
assertMatched(pattern, "a/b/src/a.c");
|
||||||
|
// no match as a "file" pattern, because rule is for directories only
|
||||||
|
assertNotMatched(pattern, "a/b/src", assume);
|
||||||
|
assertNotMatched(pattern, "a/b/srcA/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDirModeAndRegex3() {
|
||||||
|
// IgnoreRule was buggy for some cases below, therefore using "Assume"
|
||||||
|
Boolean assume = useOldRule;
|
||||||
|
|
||||||
|
String pattern = "**/src/";
|
||||||
|
assertMatched(pattern, "a/b/src/", assume);
|
||||||
|
assertMatched(pattern, "a/b/src/new", assume);
|
||||||
|
assertMatched(pattern, "a/b/src/new/a.c", assume);
|
||||||
|
assertMatched(pattern, "a/b/src/a.c", assume);
|
||||||
|
// no match as a "file" pattern, because rule is for directories only
|
||||||
|
assertNotMatched(pattern, "a/b/src", assume);
|
||||||
|
assertNotMatched(pattern, "a/b/srcA/", assume);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNameOnlyMatches() {
|
public void testNameOnlyMatches() {
|
||||||
/*
|
/*
|
||||||
|
@ -321,11 +370,16 @@ public void testNegation() {
|
||||||
* Pattern as it would appear in a .gitignore file
|
* Pattern as it would appear in a .gitignore file
|
||||||
* @param target
|
* @param target
|
||||||
* Target file path relative to repository's GIT_DIR
|
* Target file path relative to repository's GIT_DIR
|
||||||
|
* @param assume
|
||||||
*/
|
*/
|
||||||
public void assertMatched(String pattern, String target) {
|
public void assertMatched(String pattern, String target, Boolean... assume) {
|
||||||
boolean value = match(pattern, target);
|
boolean value = match(pattern, target);
|
||||||
|
if (assume.length == 0 || !assume[0].booleanValue())
|
||||||
assertTrue("Expected a match for: " + pattern + " with: " + target,
|
assertTrue("Expected a match for: " + pattern + " with: " + target,
|
||||||
value);
|
value);
|
||||||
|
else
|
||||||
|
assumeTrue("Expected a match for: " + pattern + " with: " + target,
|
||||||
|
value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -336,11 +390,17 @@ public void assertMatched(String pattern, String target) {
|
||||||
* Pattern as it would appear in a .gitignore file
|
* Pattern as it would appear in a .gitignore file
|
||||||
* @param target
|
* @param target
|
||||||
* Target file path relative to repository's GIT_DIR
|
* Target file path relative to repository's GIT_DIR
|
||||||
|
* @param assume
|
||||||
*/
|
*/
|
||||||
public void assertNotMatched(String pattern, String target) {
|
public void assertNotMatched(String pattern, String target,
|
||||||
|
Boolean... assume) {
|
||||||
boolean value = match(pattern, target);
|
boolean value = match(pattern, target);
|
||||||
assertFalse("Expected no match for: " + pattern + " with: " + target,
|
if (assume.length == 0 || !assume[0].booleanValue())
|
||||||
value);
|
assertFalse("Expected no match for: " + pattern + " with: "
|
||||||
|
+ target, value);
|
||||||
|
else
|
||||||
|
assumeFalse("Expected no match for: " + pattern + " with: "
|
||||||
|
+ target, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -355,16 +415,43 @@ public void assertNotMatched(String pattern, String target) {
|
||||||
*/
|
*/
|
||||||
private boolean match(String pattern, String target) {
|
private boolean match(String pattern, String target) {
|
||||||
boolean isDirectory = target.endsWith("/");
|
boolean isDirectory = target.endsWith("/");
|
||||||
|
boolean match;
|
||||||
if (useOldRule.booleanValue()) {
|
if (useOldRule.booleanValue()) {
|
||||||
IgnoreRule r = new IgnoreRule(pattern);
|
IgnoreRule r = new IgnoreRule(pattern);
|
||||||
// If speed of this test is ever an issue, we can use a presetRule
|
match = r.isMatch(target, isDirectory);
|
||||||
// field
|
} else {
|
||||||
// to avoid recompiling a pattern each time.
|
FastIgnoreRule r = new FastIgnoreRule(pattern);
|
||||||
return r.isMatch(target, isDirectory);
|
match = r.isMatch(target, isDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDirectory) {
|
||||||
|
boolean noTrailingSlash = matchAsDir(pattern,
|
||||||
|
target.substring(0, target.length() - 1));
|
||||||
|
if (match != noTrailingSlash) {
|
||||||
|
String message = "Difference in result for directory pattern: "
|
||||||
|
+ pattern + " with: " + target
|
||||||
|
+ " if target is given without trailing slash";
|
||||||
|
Assert.assertEquals(message, match, noTrailingSlash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* must not ends with a slash!
|
||||||
|
* @param pattern
|
||||||
|
* same as {@link #match(String, String)}
|
||||||
|
* @return same as {@link #match(String, String)}
|
||||||
|
*/
|
||||||
|
private boolean matchAsDir(String pattern, String target) {
|
||||||
|
assertFalse(target.endsWith("/"));
|
||||||
|
if (useOldRule.booleanValue()) {
|
||||||
|
IgnoreRule r = new IgnoreRule(pattern);
|
||||||
|
return r.isMatch(target, true);
|
||||||
}
|
}
|
||||||
FastIgnoreRule r = new FastIgnoreRule(pattern);
|
FastIgnoreRule r = new FastIgnoreRule(pattern);
|
||||||
// If speed of this test is ever an issue, we can use a presetRule field
|
return r.isMatch(target, true);
|
||||||
// to avoid recompiling a pattern each time.
|
|
||||||
return r.isMatch(target, isDirectory);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
package org.eclipse.jgit.lib;
|
package org.eclipse.jgit.lib;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
@ -83,4 +84,15 @@ public void nullForNameShouldThrowIllegalArgumentException() {
|
||||||
public void nullForEmailShouldThrowIllegalArgumentException() {
|
public void nullForEmailShouldThrowIllegalArgumentException() {
|
||||||
new PersonIdent("A U Thor", null);
|
new PersonIdent("A U Thor", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToExternalStringTrimsNameAndEmail() throws Exception {
|
||||||
|
PersonIdent personIdent = new PersonIdent(" A U Thor ",
|
||||||
|
" author@example.com ");
|
||||||
|
|
||||||
|
String externalString = personIdent.toExternalString();
|
||||||
|
|
||||||
|
assertTrue(externalString.startsWith("A U Thor <author@example.com>"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,8 +79,11 @@ class SimilarityIndex {
|
||||||
/** Maximum value of the count field, also mask to extract the count. */
|
/** Maximum value of the count field, also mask to extract the count. */
|
||||||
private static final long MAX_COUNT = (1L << KEY_SHIFT) - 1;
|
private static final long MAX_COUNT = (1L << KEY_SHIFT) - 1;
|
||||||
|
|
||||||
/** Total size of the file we hashed into the structure. */
|
/**
|
||||||
private long fileSize;
|
* Total amount of bytes hashed into the structure, including \n. This is
|
||||||
|
* usually the size of the file minus number of CRLF encounters.
|
||||||
|
*/
|
||||||
|
private long hashedCnt;
|
||||||
|
|
||||||
/** Number of non-zero entries in {@link #idHash}. */
|
/** Number of non-zero entries in {@link #idHash}. */
|
||||||
private int idSize;
|
private int idSize;
|
||||||
|
@ -108,48 +111,59 @@ class SimilarityIndex {
|
||||||
idGrowAt = growAt(idHashBits);
|
idGrowAt = growAt(idHashBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
long getFileSize() {
|
|
||||||
return fileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFileSize(long size) {
|
|
||||||
fileSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hash(ObjectLoader obj) throws MissingObjectException, IOException,
|
void hash(ObjectLoader obj) throws MissingObjectException, IOException,
|
||||||
TableFullException {
|
TableFullException {
|
||||||
if (obj.isLarge()) {
|
if (obj.isLarge()) {
|
||||||
ObjectStream in = obj.openStream();
|
hashLargeObject(obj);
|
||||||
try {
|
|
||||||
setFileSize(in.getSize());
|
|
||||||
hash(in, fileSize);
|
|
||||||
} finally {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
byte[] raw = obj.getCachedBytes();
|
byte[] raw = obj.getCachedBytes();
|
||||||
setFileSize(raw.length);
|
|
||||||
hash(raw, 0, raw.length);
|
hash(raw, 0, raw.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hashLargeObject(ObjectLoader obj) throws IOException,
|
||||||
|
TableFullException {
|
||||||
|
ObjectStream in1 = obj.openStream();
|
||||||
|
boolean text;
|
||||||
|
try {
|
||||||
|
text = !RawText.isBinary(in1);
|
||||||
|
} finally {
|
||||||
|
in1.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectStream in2 = obj.openStream();
|
||||||
|
try {
|
||||||
|
hash(in2, in2.getSize(), text);
|
||||||
|
} finally {
|
||||||
|
in2.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void hash(byte[] raw, int ptr, final int end) throws TableFullException {
|
void hash(byte[] raw, int ptr, final int end) throws TableFullException {
|
||||||
|
final boolean text = !RawText.isBinary(raw);
|
||||||
|
hashedCnt = 0;
|
||||||
while (ptr < end) {
|
while (ptr < end) {
|
||||||
int hash = 5381;
|
int hash = 5381;
|
||||||
|
int blockHashedCnt = 0;
|
||||||
int start = ptr;
|
int start = ptr;
|
||||||
|
|
||||||
// Hash one line, or one block, whichever occurs first.
|
// Hash one line, or one block, whichever occurs first.
|
||||||
do {
|
do {
|
||||||
int c = raw[ptr++] & 0xff;
|
int c = raw[ptr++] & 0xff;
|
||||||
|
// Ignore CR in CRLF sequence if text
|
||||||
|
if (text && c == '\r' && ptr < end && raw[ptr] == '\n')
|
||||||
|
continue;
|
||||||
|
blockHashedCnt++;
|
||||||
if (c == '\n')
|
if (c == '\n')
|
||||||
break;
|
break;
|
||||||
hash = (hash << 5) + hash + c;
|
hash = (hash << 5) + hash + c;
|
||||||
} while (ptr < end && ptr - start < 64);
|
} while (ptr < end && ptr - start < 64);
|
||||||
add(hash, ptr - start);
|
hashedCnt += blockHashedCnt;
|
||||||
|
add(hash, blockHashedCnt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hash(InputStream in, long remaining) throws IOException,
|
void hash(InputStream in, long remaining, boolean text) throws IOException,
|
||||||
TableFullException {
|
TableFullException {
|
||||||
byte[] buf = new byte[4096];
|
byte[] buf = new byte[4096];
|
||||||
int ptr = 0;
|
int ptr = 0;
|
||||||
|
@ -157,6 +171,7 @@ void hash(InputStream in, long remaining) throws IOException,
|
||||||
|
|
||||||
while (0 < remaining) {
|
while (0 < remaining) {
|
||||||
int hash = 5381;
|
int hash = 5381;
|
||||||
|
int blockHashedCnt = 0;
|
||||||
|
|
||||||
// Hash one line, or one block, whichever occurs first.
|
// Hash one line, or one block, whichever occurs first.
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
@ -170,11 +185,16 @@ void hash(InputStream in, long remaining) throws IOException,
|
||||||
|
|
||||||
n++;
|
n++;
|
||||||
int c = buf[ptr++] & 0xff;
|
int c = buf[ptr++] & 0xff;
|
||||||
|
// Ignore CR in CRLF sequence if text
|
||||||
|
if (text && c == '\r' && ptr < cnt && buf[ptr] == '\n')
|
||||||
|
continue;
|
||||||
|
blockHashedCnt++;
|
||||||
if (c == '\n')
|
if (c == '\n')
|
||||||
break;
|
break;
|
||||||
hash = (hash << 5) + hash + c;
|
hash = (hash << 5) + hash + c;
|
||||||
} while (n < 64 && n < remaining);
|
} while (n < 64 && n < remaining);
|
||||||
add(hash, n);
|
hashedCnt += blockHashedCnt;
|
||||||
|
add(hash, blockHashedCnt);
|
||||||
remaining -= n;
|
remaining -= n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,7 +213,7 @@ void sort() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int score(SimilarityIndex dst, int maxScore) {
|
int score(SimilarityIndex dst, int maxScore) {
|
||||||
long max = Math.max(fileSize, dst.fileSize);
|
long max = Math.max(hashedCnt, dst.hashedCnt);
|
||||||
if (max == 0)
|
if (max == 0)
|
||||||
return maxScore;
|
return maxScore;
|
||||||
return (int) ((common(dst) * maxScore) / max);
|
return (int) ((common(dst) * maxScore) / max);
|
||||||
|
|
|
@ -217,7 +217,8 @@ boolean iterate(final String path, final int startIncl, final int endExcl,
|
||||||
matcher++;
|
matcher++;
|
||||||
match = matches(matcher, path, left, endExcl,
|
match = matches(matcher, path, left, endExcl,
|
||||||
assumeDirectory);
|
assumeDirectory);
|
||||||
} else if (dirOnly)
|
} else if (dirOnly && !assumeDirectory)
|
||||||
|
// Directory expectations not met
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return match && matcher + 1 == matchers.size();
|
return match && matcher + 1 == matchers.size();
|
||||||
|
|
|
@ -254,9 +254,9 @@ && getEmailAddress().equals(p.getEmailAddress())
|
||||||
*/
|
*/
|
||||||
public String toExternalString() {
|
public String toExternalString() {
|
||||||
final StringBuilder r = new StringBuilder();
|
final StringBuilder r = new StringBuilder();
|
||||||
r.append(getName());
|
r.append(getName().trim());
|
||||||
r.append(" <"); //$NON-NLS-1$
|
r.append(" <"); //$NON-NLS-1$
|
||||||
r.append(getEmailAddress());
|
r.append(getEmailAddress().trim());
|
||||||
r.append("> "); //$NON-NLS-1$
|
r.append("> "); //$NON-NLS-1$
|
||||||
r.append(when / 1000);
|
r.append(when / 1000);
|
||||||
r.append(' ');
|
r.append(' ');
|
||||||
|
|
Loading…
Reference in New Issue