Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Don't convert pointers for already uploaded LFS files
- Loading branch information
Showing
2 changed files
with
144 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package git.lfs.migrate; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.nio.charset.CharacterCodingException; | ||
import java.nio.charset.CharsetDecoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Map; | ||
import java.util.TreeMap; | ||
|
||
/** | ||
* Class for read/writer pointer blobs. | ||
* https://github.com/github/git-lfs/blob/master/docs/spec.md | ||
* | ||
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com> | ||
*/ | ||
public class LfsPointer { | ||
public static final int POINTER_MAX_SIZE = 1024; | ||
@NotNull | ||
public static final String OID = "oid"; | ||
@NotNull | ||
public static final String SIZE = "size"; | ||
|
||
@NotNull | ||
private static final String[] REQUIRED = { | ||
OID, | ||
SIZE, | ||
}; | ||
@NotNull | ||
private static final byte[] PREFIX = "version ".getBytes(StandardCharsets.UTF_8); | ||
|
||
/** | ||
* Read pointer data. | ||
* | ||
* @param blob Blob data. | ||
* @return Return pointer info or null if blob is not a pointer data. | ||
*/ | ||
@Nullable | ||
public static Map<String, String> parsePointer(@NotNull byte[] blob) { | ||
return parsePointer(blob, 0, blob.length); | ||
} | ||
|
||
/** | ||
* Read pointer data. | ||
* | ||
* @param blob Blob data. | ||
* @return Return pointer info or null if blob is not a pointer data. | ||
*/ | ||
@Nullable | ||
public static Map<String, String> parsePointer(@NotNull byte[] blob, final int offset, final int length) { | ||
// Check prefix | ||
if (length < PREFIX.length) return null; | ||
for (int i = 0; i < PREFIX.length; ++i) { | ||
if (blob[i] != PREFIX[i]) return null; | ||
} | ||
// Reading key value pairs | ||
final TreeMap<String, String> result = new TreeMap<>(); | ||
final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); | ||
String lastKey = null; | ||
int keyOffset = offset; | ||
int required = 0; | ||
while (true) { | ||
if (keyOffset >= length) { | ||
break; | ||
} | ||
int valueOffset = keyOffset; | ||
// Key | ||
while (true) { | ||
valueOffset++; | ||
if (valueOffset < length) { | ||
final byte c = blob[valueOffset]; | ||
if (c == ' ') break; | ||
// Keys MUST only use the characters [a-z] [0-9] . -. | ||
if (c >= 'a' && c <= 'z') continue; | ||
if (c >= '0' && c <= '9') continue; | ||
if (c == '.' || c == '-') continue; | ||
} | ||
// Found invalid character. | ||
return null; | ||
} | ||
int endOffset = valueOffset; | ||
// Value | ||
while (true) { | ||
endOffset++; | ||
if (endOffset >= length) return null; | ||
// Values MUST NOT contain return or newline characters. | ||
if (blob[endOffset] == '\n') break; | ||
} | ||
final String key = new String(blob, keyOffset, valueOffset - keyOffset, StandardCharsets.UTF_8); | ||
|
||
final String value; | ||
try { | ||
value = decoder.decode(ByteBuffer.wrap(blob, valueOffset + 1, endOffset - valueOffset - 1)).toString(); | ||
} catch (CharacterCodingException e) { | ||
return null; | ||
} | ||
if (required < REQUIRED.length && REQUIRED[required].equals(key)) { | ||
required++; | ||
} | ||
if (keyOffset > offset) { | ||
if (lastKey != null && key.compareTo(lastKey) <= 0) { | ||
return null; | ||
} | ||
lastKey = key; | ||
} | ||
if (result.put(key, value) != null) { | ||
return null; | ||
} | ||
keyOffset = endOffset + 1; | ||
} | ||
// Not found all required fields. | ||
if (required < REQUIRED.length) { | ||
return null; | ||
} | ||
try { | ||
if (Long.parseLong(result.get(SIZE)) < 0) { | ||
return null; | ||
} | ||
} catch (NumberFormatException ignored) { | ||
return null; | ||
} | ||
return result; | ||
} | ||
} |