|
@@ -0,0 +1,402 @@
|
|
|
|
|
+package info.knacki.gitdroid.entities;
|
|
|
|
|
+
|
|
|
|
|
+import android.support.annotation.NonNull;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.FileInputStream;
|
|
|
|
|
+import java.io.FileNotFoundException;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.nio.charset.Charset;
|
|
|
|
|
+import java.util.ArrayDeque;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.SortedSet;
|
|
|
|
|
+import java.util.TreeSet;
|
|
|
|
|
+import java.util.logging.Logger;
|
|
|
|
|
+
|
|
|
|
|
+import info.knacki.gitdroid.GitSha1;
|
|
|
|
|
+
|
|
|
|
|
+public abstract class GitObject implements Comparable<GitObject>, GitPackable {
|
|
|
|
|
+ GitTree fParent;
|
|
|
|
|
+ final byte[] fMode;
|
|
|
|
|
+ final String fName;
|
|
|
|
|
+ byte[] fSha1;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public int compareTo(@NonNull GitObject gitObject) {
|
|
|
|
|
+ return fName.toLowerCase().compareTo(gitObject.fName.toLowerCase());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static class GitBlob extends GitObject {
|
|
|
|
|
+ private File fFile;
|
|
|
|
|
+
|
|
|
|
|
+ private GitBlob(GitTree parent, byte[] mode, String name, byte[] sha1) {
|
|
|
|
|
+ super(parent, mode, name, sha1);
|
|
|
|
|
+ fFile = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private GitBlob(GitTree parent, GitBlob copy) {
|
|
|
|
|
+ this(parent, copy.fMode, copy.fName, copy.fSha1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ GitBlob(GitTree parent, String filename, File f) {
|
|
|
|
|
+ this(parent, new byte[] { 49, 48, 48, 54, 52, 52 }, filename, GitSha1.getRawSha1OfFile(f));
|
|
|
|
|
+ fFile = f;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public eType GetPackableType() {
|
|
|
|
|
+ return eType.eType_Blob;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static byte[] GetFilePack(File f) throws IOException {
|
|
|
|
|
+ ByteArrayOutputStream str = new ByteArrayOutputStream();
|
|
|
|
|
+ int len;
|
|
|
|
|
+ byte[] buf = new byte[1024];
|
|
|
|
|
+ FileInputStream in = new FileInputStream(f);
|
|
|
|
|
+ do {
|
|
|
|
|
+ len = in.read(buf, 0, 1024);
|
|
|
|
|
+ if (len > 0) {
|
|
|
|
|
+ str.write(buf, 0, len);
|
|
|
|
|
+ }
|
|
|
|
|
+ } while (len > 0);
|
|
|
|
|
+ return str.toByteArray();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public byte[] GetPack() {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ return GetFilePack(fFile);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (FileNotFoundException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch(IOException t) {
|
|
|
|
|
+ t.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static class GitRawData extends GitBlob {
|
|
|
|
|
+ private final byte[] fData;
|
|
|
|
|
+
|
|
|
|
|
+ GitRawData(byte[] sha1, byte[] data) {
|
|
|
|
|
+ super(null, new byte[] { 49, 48, 48, 54, 52, 52 }, "", sha1);
|
|
|
|
|
+ fData = data;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitRawData(GitTree parent, byte[] mode, String name, byte[] sha1) {
|
|
|
|
|
+ super(parent, mode, name, sha1);
|
|
|
|
|
+ fData = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitRawData(GitTree parent, GitBlob emptyBlob, GitRawData prev) {
|
|
|
|
|
+ super(parent, new byte[] { 49, 48, 48, 54, 52, 52 }, emptyBlob.GetFilename(), prev.GetHash());
|
|
|
|
|
+ fData = prev.fData;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public eType GetPackableType() {
|
|
|
|
|
+ return eType.eType_Blob;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public byte[] GetPack() {
|
|
|
|
|
+ return GetData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public byte[] GetData() { return fData; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static class GitTree extends GitObject {
|
|
|
|
|
+ HashMap<String, GitObject> fItems = null;
|
|
|
|
|
+ ArrayDeque<String> fItemOrder;
|
|
|
|
|
+
|
|
|
|
|
+ private GitTree(GitTree parent, byte[] mode, String name, byte[] sha1) {
|
|
|
|
|
+ super(parent, mode, name, sha1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ GitTree(GitTree parent, GitTree copy) {
|
|
|
|
|
+ this(parent, copy.fMode, copy.fName, copy.fSha1);
|
|
|
|
|
+ Initialize();
|
|
|
|
|
+ for (Map.Entry<String, GitObject> o: copy.fItems.entrySet()) {
|
|
|
|
|
+ GitObject src = o.getValue();
|
|
|
|
|
+ GitObject oCopy = src instanceof GitTree ? new GitTree(this, (GitTree) src) : new GitBlob(this, (GitBlob) src);
|
|
|
|
|
+ fItems.put(o.getKey(), oCopy);
|
|
|
|
|
+ }
|
|
|
|
|
+ for (String i: copy.fItemOrder)
|
|
|
|
|
+ fItemOrder.push(i);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ GitTree(byte[] sha1) {
|
|
|
|
|
+ super(null, new byte[] { '4', '0', '0', '0', '0' }, "", sha1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private GitTree(GitTree parent, String filename) {
|
|
|
|
|
+ super(parent, new byte[] { '4', '0', '0', '0', '0' }, filename, null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitTree(GitTree parent, GitTree content, String filename) {
|
|
|
|
|
+ this(parent, filename);
|
|
|
|
|
+ fItems = content.fItems;
|
|
|
|
|
+ fItemOrder = content.fItemOrder;
|
|
|
|
|
+ for (GitObject i: fItems.values()) {
|
|
|
|
|
+ i.SetParent(this);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static GitTree CreateEmpty() {
|
|
|
|
|
+ GitTree tree = new GitTree(null);
|
|
|
|
|
+ return tree.Initialize();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitTree Initialize() {
|
|
|
|
|
+ fItems = new HashMap<>();
|
|
|
|
|
+ fItemOrder = new ArrayDeque<>();
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ boolean IsInitialized() {
|
|
|
|
|
+ return null != fItems;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitObject GetObject(String name) {
|
|
|
|
|
+ return fItems.get(name);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitObject GetObjectFullPath(String path) {
|
|
|
|
|
+ if ('/' == path.charAt(0))
|
|
|
|
|
+ path = path.substring(1);
|
|
|
|
|
+ final int nextSlash = path.indexOf('/');
|
|
|
|
|
+ final String filename = nextSlash == -1 ? path : (path.substring(0, nextSlash));
|
|
|
|
|
+ final GitObject obj = GetObject(filename);
|
|
|
|
|
+ if (obj == null || (obj instanceof GitBlob && nextSlash != -1))
|
|
|
|
|
+ return null;
|
|
|
|
|
+ if (nextSlash == -1)
|
|
|
|
|
+ return obj;
|
|
|
|
|
+ return ((GitTree) obj).GetObjectFullPath(path.substring(nextSlash +1));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitTree AddItem(GitObject item) {
|
|
|
|
|
+ fItems.put(item.fName, item);
|
|
|
|
|
+ fItemOrder.push(item.fName);
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitTree FindNextNotInitializedTree() {
|
|
|
|
|
+ if (!IsInitialized())
|
|
|
|
|
+ return this;
|
|
|
|
|
+ for (GitObject obj: fItems.values()) {
|
|
|
|
|
+ if (!(obj instanceof GitTree))
|
|
|
|
|
+ continue;
|
|
|
|
|
+ GitTree child = ((GitTree) obj).FindNextNotInitializedTree();
|
|
|
|
|
+ if (child != null)
|
|
|
|
|
+ return child;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Iterable<GitObject> GetObjects() {
|
|
|
|
|
+ ArrayDeque<GitObject> objects = new ArrayDeque<>();
|
|
|
|
|
+ for (String i: fItemOrder)
|
|
|
|
|
+ objects.push(fItems.get(i));
|
|
|
|
|
+ return objects;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Iterable<GitBlob> GetBlobs() {
|
|
|
|
|
+ SortedSet<GitBlob> objects = new TreeSet<>();
|
|
|
|
|
+ for (GitObject i: fItems.values())
|
|
|
|
|
+ if (i instanceof GitBlob)
|
|
|
|
|
+ objects.add((GitBlob) i);
|
|
|
|
|
+ return objects;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Iterable<GitTree> GetTrees() {
|
|
|
|
|
+ SortedSet<GitTree> objects = new TreeSet<>();
|
|
|
|
|
+ for (GitObject i: fItems.values())
|
|
|
|
|
+ if (i instanceof GitTree)
|
|
|
|
|
+ objects.add((GitTree) i);
|
|
|
|
|
+ return objects;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String GetPath() {
|
|
|
|
|
+ if (fParent != null)
|
|
|
|
|
+ return fParent.GetPath() +GetFilename() +"/";
|
|
|
|
|
+ return GetFilename() +"/";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void SetRealObject(String key, GitObject obj) {
|
|
|
|
|
+ if (fItems.containsKey(key)) {
|
|
|
|
|
+ fItems.put(key, obj);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void FindAllBlobs(HashMap<String, GitBlob> data) {
|
|
|
|
|
+ String rootPath = GetPath();
|
|
|
|
|
+ for (GitBlob i: GetBlobs())
|
|
|
|
|
+ data.put(rootPath +i.GetFilename(), i);
|
|
|
|
|
+ for (GitTree i: GetTrees())
|
|
|
|
|
+ i.FindAllBlobs(data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public HashMap<String, GitBlob> FindAllBlobs() {
|
|
|
|
|
+ HashMap<String, GitBlob> result = new HashMap<>();
|
|
|
|
|
+ FindAllBlobs(result);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public boolean IsRoot() {
|
|
|
|
|
+ return fParent == null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitObject AddItem(String path, File f) {
|
|
|
|
|
+ if ('/' == path.charAt(0))
|
|
|
|
|
+ path = path.substring(1);
|
|
|
|
|
+ final int nextSlash = path.indexOf('/');
|
|
|
|
|
+ final String filename = nextSlash == -1 ? path : (path.substring(0, nextSlash));
|
|
|
|
|
+ final GitObject obj = GetObject(filename);
|
|
|
|
|
+ final GitObject newItem;
|
|
|
|
|
+
|
|
|
|
|
+ if (obj == null) {
|
|
|
|
|
+ if (nextSlash != -1) {
|
|
|
|
|
+ GitTree child = new GitTree(this, filename).Initialize();
|
|
|
|
|
+ newItem = child.AddItem(path.substring(nextSlash + 1), f);
|
|
|
|
|
+ fItems.put(filename, child);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ newItem = new GitBlob(this, filename, f);
|
|
|
|
|
+ fItems.put(filename, newItem);
|
|
|
|
|
+ }
|
|
|
|
|
+ fItemOrder.push(filename);
|
|
|
|
|
+ } else if (nextSlash == -1) {
|
|
|
|
|
+ Remove(filename);
|
|
|
|
|
+ newItem = new GitBlob(this, filename, f);
|
|
|
|
|
+ fItems.put(filename, newItem);
|
|
|
|
|
+ fItemOrder.push(filename);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ newItem = ((GitTree) obj).AddItem(path.substring(nextSlash + 1), f);
|
|
|
|
|
+ }
|
|
|
|
|
+ fSha1 = null;
|
|
|
|
|
+ return newItem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitTree Remove(String filename) {
|
|
|
|
|
+ fItems.remove(filename);
|
|
|
|
|
+ fItemOrder.remove(filename);
|
|
|
|
|
+ fSha1 = null;
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Fill(byte[] data) {
|
|
|
|
|
+ int i = 0;
|
|
|
|
|
+
|
|
|
|
|
+ while (i < data.length) {
|
|
|
|
|
+ byte[] mode = null;
|
|
|
|
|
+ int len;
|
|
|
|
|
+ for (len =0; i +len <data.length && len < 8; ++len) {
|
|
|
|
|
+ if (data[i + len] == ' ') {
|
|
|
|
|
+ mode = new byte[len];
|
|
|
|
|
+ System.arraycopy(data, i, mode, 0, len);
|
|
|
|
|
+ i += len + 1;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mode == null)
|
|
|
|
|
+ break;
|
|
|
|
|
+ len =0;
|
|
|
|
|
+ while (i +len < data.length && data[i +len] != 0)
|
|
|
|
|
+ ++len;
|
|
|
|
|
+ byte[] filenameBytes = new byte[len];
|
|
|
|
|
+ System.arraycopy(data, i, filenameBytes, 0, len);
|
|
|
|
|
+ i += len +1;
|
|
|
|
|
+ if (i +20 > data.length)
|
|
|
|
|
+ break;
|
|
|
|
|
+ byte []sha1 = new byte[20];
|
|
|
|
|
+ System.arraycopy(data, i, sha1, 0, 20);
|
|
|
|
|
+ i += 20;
|
|
|
|
|
+ AddItem(GitObject.factory(this, mode, new String(filenameBytes, Charset.defaultCharset()), sha1));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public eType GetPackableType() {
|
|
|
|
|
+ return eType.eType_Tree;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public byte[] GetPack() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ByteArrayOutputStream str = new ByteArrayOutputStream();
|
|
|
|
|
+
|
|
|
|
|
+ for (GitObject go : GetObjects()) {
|
|
|
|
|
+ str.write((go.GetMode() + ' ').getBytes());
|
|
|
|
|
+ if (go.fName.equals("Aa") || "Oo".equals(go.fName))
|
|
|
|
|
+ Logger.getLogger(GitObject.class.getName()).severe("out file in tree " +go.GetGitPath() +"$" + GitSha1.BytesToString(go.GetHash()));
|
|
|
|
|
+ str.write(go.GetFilename().getBytes());
|
|
|
|
|
+ str.write(new byte[]{0});
|
|
|
|
|
+ str.write(go.GetHash());
|
|
|
|
|
+ }
|
|
|
|
|
+ return str.toByteArray();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (IOException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public byte[] GetHash() {
|
|
|
|
|
+ if (null != fSha1)
|
|
|
|
|
+ return fSha1;
|
|
|
|
|
+ return fSha1 = GitSha1.getRawSha1OfPackable(this);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private GitObject(GitTree parent, byte[] mode, String name, byte[] sha1) {
|
|
|
|
|
+ fParent = parent;
|
|
|
|
|
+ fMode = mode;
|
|
|
|
|
+ fName = name;
|
|
|
|
|
+ fSha1 = sha1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static GitObject factory(GitTree parent, byte[] mode, String name, byte[] sha1) {
|
|
|
|
|
+ if (mode[0] == '4') {
|
|
|
|
|
+ return new GitTree(parent, mode, name, sha1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return new GitRawData(parent, mode, name, sha1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public byte[] GetHash() {
|
|
|
|
|
+ return fSha1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String GetFilename() {
|
|
|
|
|
+ return fName;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String GetGitPath() {
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ GitTree parent = fParent;
|
|
|
|
|
+ sb.insert(0, fName);
|
|
|
|
|
+ while (parent != null) {
|
|
|
|
|
+ sb.insert(0, parent.fName + "/");
|
|
|
|
|
+ parent = parent.fParent;
|
|
|
|
|
+ }
|
|
|
|
|
+ return sb.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GitTree GetParent() {
|
|
|
|
|
+ return fParent;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void SetParent(GitTree t) { fParent = t; }
|
|
|
|
|
+
|
|
|
|
|
+ public String GetMode() {
|
|
|
|
|
+ return new String(fMode);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public int GetDepth() {
|
|
|
|
|
+ return fParent == null ? 1 : fParent.GetDepth() +1;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|