From 1fcec67a3226dc43335931461d9f8c11ceb22369 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Mar 2006 23:07:57 -0500 Subject: [PATCH] Initial set of files. This stuff barely works, but it can read a ref, commit and a tree. Patch: Initial-Files --- .classpath | 8 + .gitignore | 1 + .project | 17 +++ .settings/org.eclipse.jdt.ui.prefs | 4 + jgit--All-Tests.launch | 13 ++ src/org/spearce/jgit/lib/Commit.java | 114 +++++++++++++++ .../spearce/jgit/lib/CorruptObjectException.java | 11 ++ src/org/spearce/jgit/lib/FileCopier.java | 53 +++++++ src/org/spearce/jgit/lib/ObjectDatabase.java | 146 +++++++++++++++++++ src/org/spearce/jgit/lib/ObjectId.java | 82 +++++++++++ src/org/spearce/jgit/lib/ObjectReader.java | 85 +++++++++++ src/org/spearce/jgit/lib/Tree.java | 161 +++++++++++++++++++++ src/org/spearce/jgit/lib/Treeish.java | 10 ++ .../spearce/jgit/lib_tst/ObjectDatabaseTest.java | 92 ++++++++++++ tst/org/spearce/jgit/lib_tst/ObjectIdTest.java | 27 ++++ 15 files changed, 824 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.ui.prefs create mode 100644 jgit--All-Tests.launch create mode 100644 src/org/spearce/jgit/lib/Commit.java create mode 100644 src/org/spearce/jgit/lib/CorruptObjectException.java create mode 100644 src/org/spearce/jgit/lib/FileCopier.java create mode 100644 src/org/spearce/jgit/lib/ObjectDatabase.java create mode 100644 src/org/spearce/jgit/lib/ObjectId.java create mode 100644 src/org/spearce/jgit/lib/ObjectReader.java create mode 100644 src/org/spearce/jgit/lib/Tree.java create mode 100644 src/org/spearce/jgit/lib/Treeish.java create mode 100644 tst/org/spearce/jgit/lib_tst/ObjectDatabaseTest.java create mode 100644 tst/org/spearce/jgit/lib_tst/ObjectIdTest.java diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..274dde6a --- /dev/null +++ b/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ba077a40 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin diff --git a/.project b/.project new file mode 100644 index 00000000..c9c2ae0d --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + jgit + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000..8fc7a82a --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,4 @@ +#Mon Mar 06 13:32:24 EST 2006 +eclipse.preferences.version=1 +formatter_settings_version=8 +org.eclipse.jdt.ui.text.custom_code_templates=\n diff --git a/jgit--All-Tests.launch b/jgit--All-Tests.launch new file mode 100644 index 00000000..ee312cf5 --- /dev/null +++ b/jgit--All-Tests.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/org/spearce/jgit/lib/Commit.java b/src/org/spearce/jgit/lib/Commit.java new file mode 100644 index 00000000..0544bc81 --- /dev/null +++ b/src/org/spearce/jgit/lib/Commit.java @@ -0,0 +1,114 @@ +package org.spearce.jgit.lib; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Commit implements Treeish { + private final ObjectDatabase objdb; + + private final ObjectId commitId; + + private final ObjectId treeId; + + private final List parentIds; + + private final String author; + + private final String committer; + + private final String message; + + private Tree treeObj; + + public Commit(final ObjectDatabase db, final ObjectId id, + final BufferedReader br) throws IOException { + objdb = db; + commitId = id; + + final ArrayList tempParents; + final StringBuffer tempMessage; + final char[] readBuf; + int readLen; + String line; + + line = br.readLine(); + if (line == null || !line.startsWith("tree ")) { + throw new CorruptObjectException("No tree found in commit " + id); + } + treeId = new ObjectId(line.substring("tree ".length())); + + tempParents = new ArrayList(2); + for (;;) { + line = br.readLine(); + if (line == null) { + throw new CorruptObjectException("Commit header corrupt " + id); + } + if (line.startsWith("parent ")) { + tempParents + .add(new ObjectId(line.substring("parent ".length()))); + } else { + break; + } + } + parentIds = Collections.unmodifiableList(tempParents); + + if (line == null || !line.startsWith("author ")) { + throw new CorruptObjectException("No author found in commit " + id); + } + author = line.substring("author ".length()); + + line = br.readLine(); + if (line == null || !line.startsWith("committer ")) { + throw new CorruptObjectException("No committer found in commit " + + id); + } + committer = line.substring("committer ".length()); + + line = br.readLine(); + if (line == null || !line.equals("")) { + throw new CorruptObjectException( + "No blank line after header in commit " + id); + } + + tempMessage = new StringBuffer(); + readBuf = new char[128]; + while ((readLen = br.read(readBuf)) > 0) { + tempMessage.append(readBuf, 0, readLen); + } + message = tempMessage.toString(); + } + + public ObjectId getCommitId() { + return commitId; + } + + public ObjectId getTreeId() { + return treeId; + } + + public List getTreeEntries() throws IOException { + if (treeObj == null) { + treeObj = objdb.openTree(getTreeId()); + } + return treeObj.getTreeEntries(); + } + + public String getAuthor() { + return author; + } + + public String getCommitter() { + return committer; + } + + public List getParentIds() { + return parentIds; + } + + public String getMessage() { + return message; + } +} diff --git a/src/org/spearce/jgit/lib/CorruptObjectException.java b/src/org/spearce/jgit/lib/CorruptObjectException.java new file mode 100644 index 00000000..57691dc5 --- /dev/null +++ b/src/org/spearce/jgit/lib/CorruptObjectException.java @@ -0,0 +1,11 @@ +package org.spearce.jgit.lib; + +import java.io.IOException; + +public class CorruptObjectException extends IOException { + private static final long serialVersionUID = 1L; + + public CorruptObjectException(final String message) { + super(message); + } +} diff --git a/src/org/spearce/jgit/lib/FileCopier.java b/src/org/spearce/jgit/lib/FileCopier.java new file mode 100644 index 00000000..199607c2 --- /dev/null +++ b/src/org/spearce/jgit/lib/FileCopier.java @@ -0,0 +1,53 @@ +package org.spearce.jgit.lib; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +public class FileCopier { + public void copyOut(final Tree src, final File dest) throws IOException { + dest.mkdirs(); + copyOut(src, dest, new byte[8192]); + } + + protected void copyOut(final Tree src, final File dest, + final byte[] copyBuffer) throws IOException { + final Iterator i; + + i = src.getTreeEntries().iterator(); + while (i.hasNext()) { + final Tree.Entry e = (Tree.Entry) i.next(); + final File f = new File(dest, e.getName()); + + if (e.isTree()) { + f.mkdir(); + copyOut(e.getTree(), f, copyBuffer); + } else if (e.isSymlink()) { + // TODO: We don't handle symlinks right now. + } else { + final ObjectReader or = e.openBlob(); + final InputStream is; + if (or == null) { + throw new CorruptObjectException("Missing blob " + + e.getId()); + } + is = or.getInputStream(); + try { + final FileOutputStream fos = new FileOutputStream(f); + try { + int r; + while ((r = is.read(copyBuffer)) > 0) { + fos.write(copyBuffer, 0, r); + } + } finally { + fos.close(); + } + } finally { + or.close(); + } + } + } + } +} diff --git a/src/org/spearce/jgit/lib/ObjectDatabase.java b/src/org/spearce/jgit/lib/ObjectDatabase.java new file mode 100644 index 00000000..41677b82 --- /dev/null +++ b/src/org/spearce/jgit/lib/ObjectDatabase.java @@ -0,0 +1,146 @@ +package org.spearce.jgit.lib; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; + +public class ObjectDatabase { + private static final String[] refSearchPaths = { "", "refs/", "refs/tags/", + "refs/heads/", }; + + private final File db; + + private final File objectsDir; + + public ObjectDatabase(final File d) { + db = d.getAbsoluteFile(); + objectsDir = new File(db, "objects"); + } + + private InputStream openObjectStream(final ObjectId objectId) + throws IOException { + try { + final String n = objectId.toString(); + return new FileInputStream(new File(new File(objectsDir, n + .substring(0, 2)), n.substring(2))); + } catch (FileNotFoundException fnfe) { + return null; + } + } + + public ObjectReader openBlob(final ObjectId id) throws IOException { + final InputStream fis = openObjectStream(id); + if (fis == null) { + return null; + } + + try { + final ObjectReader or = new ObjectReader(id, fis); + if ("blob".equals(or.getType())) { + return or; + } else { + throw new CorruptObjectException("Not a blob " + id); + } + } catch (IOException ioe) { + fis.close(); + throw ioe; + } + } + + public Commit openCommit(final ObjectId id) throws IOException { + final InputStream fis = openObjectStream(id); + if (fis == null) { + return null; + } + + try { + final ObjectReader or = new ObjectReader(id, fis); + try { + if ("commit".equals(or.getType())) { + return new Commit(this, id, or.getBufferedReader()); + } else { + throw new CorruptObjectException("Not a commit: " + id); + } + } finally { + or.close(); + } + } catch (IOException ioe) { + fis.close(); + throw ioe; + } + } + + public Tree openTree(final ObjectId id) throws IOException { + final InputStream fis = openObjectStream(id); + if (fis == null) { + return null; + } + + try { + final ObjectReader or = new ObjectReader(id, fis); + try { + if ("commit".equals(or.getType())) { + return openTree(new Commit(this, id, or.getBufferedReader()) + .getTreeId()); + } else if ("tree".equals(or.getType())) { + return new Tree(this, id, or.getInputStream()); + } else { + throw new CorruptObjectException("Not a tree-ish: " + id); + } + } finally { + or.close(); + } + } catch (IOException ioe) { + fis.close(); + throw ioe; + } + } + + private ObjectId readRef(final String name) throws IOException { + final File f = new File(db, name); + if (!f.isFile()) { + return null; + } + final BufferedReader fr = new BufferedReader(new FileReader(f)); + try { + final String line = fr.readLine(); + if (line == null || line.length() == 0) { + return null; + } + if (line.startsWith("ref: ")) { + return readRef(line.substring("ref: ".length())); + } + if (ObjectId.isId(line)) { + return new ObjectId(line); + } + throw new IOException("Not a ref: " + name + ": " + line); + } finally { + fr.close(); + } + } + + public ObjectId resolveRevision(final String r) throws IOException { + ObjectId id = null; + + if (ObjectId.isId(r)) { + id = new ObjectId(r); + } + if (id == null) { + for (int k = 0; k < refSearchPaths.length; k++) { + id = readRef(refSearchPaths[k] + r); + if (id != null) { + break; + } + } + } + return id; + } + + public String toString() { + return "ObjectDatabase[" + db + "]"; + } +} diff --git a/src/org/spearce/jgit/lib/ObjectId.java b/src/org/spearce/jgit/lib/ObjectId.java new file mode 100644 index 00000000..f09f1be0 --- /dev/null +++ b/src/org/spearce/jgit/lib/ObjectId.java @@ -0,0 +1,82 @@ +package org.spearce.jgit.lib; + +public class ObjectId { + public static final boolean isId(final String id) { + if (id.length() != 40) { + return false; + } + for (int k = 0; k < 40; k++) { + final char c = id.charAt(k); + if ('0' <= c && c <= '9') { + continue; + } else if ('a' <= c && c <= 'f') { + continue; + } else { + return false; + } + } + return true; + } + + private final byte[] id; + + public ObjectId(final String i) { + id = new byte[20]; + for (int j = 0, k = 0; k < 20; k++) { + final char c1 = i.charAt(j++); + final char c2 = i.charAt(j++); + int b; + + if ('0' <= c1 && c1 <= '9') { + b = c1 - '0'; + } else { + b = c1 - 'a' + 10; + } + b <<= 4; + if ('0' <= c2 && c2 <= '9') { + b |= c2 - '0'; + } else { + b |= c2 - 'a' + 10; + } + id[k] = (byte) b; + } + } + + public ObjectId(final byte[] i) { + id = i; + } + + public int hashCode() { + int r = 0; + for (int k = 0; k < 20; k++) { + r *= 31; + r += id[k]; + } + return r; + } + + public boolean equals(final Object o) { + if (o instanceof ObjectId) { + final byte[] o_id = ((ObjectId) o).id; + for (int k = 0; k < 20; k++) { + if (id[k] != o_id[k]) { + return false; + } + } + return true; + } + return false; + } + + public String toString() { + final StringBuffer r = new StringBuffer(40); + for (int k = 0; k < 20; k++) { + final int b = id[k]; + final int b1 = (b >> 4) & 0xf; + final int b2 = b & 0xf; + r.append(b1 < 10 ? (char) ('0' + b1) : (char) ('a' + b1 - 10)); + r.append(b2 < 10 ? (char) ('0' + b2) : (char) ('a' + b2 - 10)); + } + return r.toString(); + } +} diff --git a/src/org/spearce/jgit/lib/ObjectReader.java b/src/org/spearce/jgit/lib/ObjectReader.java new file mode 100644 index 00000000..f0b0fee5 --- /dev/null +++ b/src/org/spearce/jgit/lib/ObjectReader.java @@ -0,0 +1,85 @@ +package org.spearce.jgit.lib; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.zip.InflaterInputStream; + +public class ObjectReader { + private final ObjectId objectId; + + private final String objectType; + + private final int objectSize; + + private InflaterInputStream inflater; + + public ObjectReader(final ObjectId id, final InputStream src) + throws IOException { + objectId = id; + inflater = new InflaterInputStream(src); + + final StringBuffer tempType = new StringBuffer(16); + int tempSize = 0; + int c; + + for (;;) { + c = inflater.read(); + if (' ' == c) { + break; + } + if (c < 'a' || c > 'z') { + throw new CorruptObjectException("Corrupt header in " + + objectId); + } + if (tempType.length() >= 16) { + throw new CorruptObjectException("Type header exceed limit in " + + objectId); + } + tempType.append((char) c); + } + objectType = tempType.toString(); + + for (;;) { + c = inflater.read(); + if (0 == c) { + break; + } + if (c < '0' || c > '9') { + throw new CorruptObjectException("Corrupt header in " + + objectId); + } + tempSize *= 10; + tempSize += c - '0'; + } + objectSize = tempSize; + } + + public ObjectId getId() { + return objectId; + } + + public String getType() { + return objectType; + } + + public int getSize() { + return objectSize; + } + + public BufferedReader getBufferedReader() + throws UnsupportedEncodingException { + return new BufferedReader(new InputStreamReader(inflater, "UTF-8")); + } + + public InputStream getInputStream() { + return inflater; + } + + public void close() throws IOException { + inflater.close(); + inflater = null; + } +} diff --git a/src/org/spearce/jgit/lib/Tree.java b/src/org/spearce/jgit/lib/Tree.java new file mode 100644 index 00000000..b62a67cb --- /dev/null +++ b/src/org/spearce/jgit/lib/Tree.java @@ -0,0 +1,161 @@ +package org.spearce.jgit.lib; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class Tree implements Treeish { + private final ObjectDatabase objdb; + + private final ObjectId treeId; + + private final List entries; + + public Tree(final ObjectDatabase db, final ObjectId id, final InputStream is) + throws IOException { + objdb = db; + treeId = id; + + final ArrayList tempEnts = new ArrayList(); + for (;;) { + int c; + int mode; + final ByteArrayOutputStream nameBuf; + final byte[] entId; + int entIdLen; + + c = is.read(); + if (c == -1) { + break; + } + if (c < '0' || c > '7') { + throw new CorruptObjectException("Invalid tree entry in " + id); + } + mode = c - '0'; + for (;;) { + c = is.read(); + if (' ' == c) { + break; + } + if (c < '0' || c > '7') { + throw new CorruptObjectException("Invalid tree entry in " + + id); + } + mode *= 8; + mode += c - '0'; + } + + nameBuf = new ByteArrayOutputStream(128); + for (;;) { + c = is.read(); + if (c == -1) { + throw new CorruptObjectException("Invalid tree entry in " + + id); + } + if (0 == c) { + break; + } + nameBuf.write(c); + } + + entId = new byte[20]; + entIdLen = 0; + while ((c = is.read(entId, entIdLen, entId.length - entIdLen)) > 0) { + entIdLen += c; + } + if (entIdLen != entId.length) { + throw new CorruptObjectException("Invalid tree entry in " + id); + } + + tempEnts.add(new Entry(mode, new String(nameBuf.toByteArray(), + "UTF-8"), new ObjectId(entId))); + } + + entries = tempEnts; + } + + public ObjectId getTreeId() { + return treeId; + } + + public List getTreeEntries() { + return entries; + } + + public class Entry { + private final int mode; + + private final String name; + + private final ObjectId id; + + private Tree treeObj; + + private Entry(final int mode, final String name, final ObjectId id) { + this.mode = mode; + this.name = name; + this.id = id; + } + + public boolean isTree() { + return (getMode() & 040000) != 0; + } + + public boolean isSymlink() { + return (getMode() & 020000) != 0; + } + + public boolean isExecutable() { + return (getMode() & 0100) != 0; + } + + public String getName() { + return name; + } + + public int getMode() { + return mode; + } + + public ObjectId getId() { + return id; + } + + public Tree getTree() throws IOException { + if (treeObj == null) { + treeObj = objdb.openTree(getId()); + } + return treeObj; + } + + public ObjectReader openBlob() throws IOException { + return objdb.openBlob(getId()); + } + + public String toString() { + final StringBuffer r = new StringBuffer(); + final String modeStr = Integer.toString(getMode(), 8); + r.append(getId()); + r.append(' '); + if (isTree()) { + r.append('D'); + } else if (isSymlink()) { + r.append('S'); + } else if (isExecutable()) { + r.append('X'); + } else { + r.append('F'); + } + r.append(' '); + if (modeStr.length() == 5) { + r.append('0'); + } + r.append(modeStr); + r.append(' '); + r.append(getName()); + return r.toString(); + } + } +} diff --git a/src/org/spearce/jgit/lib/Treeish.java b/src/org/spearce/jgit/lib/Treeish.java new file mode 100644 index 00000000..15c72980 --- /dev/null +++ b/src/org/spearce/jgit/lib/Treeish.java @@ -0,0 +1,10 @@ +package org.spearce.jgit.lib; + +import java.io.IOException; +import java.util.List; + +public interface Treeish { + public ObjectId getTreeId(); + + public List getTreeEntries() throws IOException; +} diff --git a/tst/org/spearce/jgit/lib_tst/ObjectDatabaseTest.java b/tst/org/spearce/jgit/lib_tst/ObjectDatabaseTest.java new file mode 100644 index 00000000..b87d43f9 --- /dev/null +++ b/tst/org/spearce/jgit/lib_tst/ObjectDatabaseTest.java @@ -0,0 +1,92 @@ +package org.spearce.jgit.lib_tst; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +import junit.framework.TestCase; + +import org.spearce.jgit.lib.Commit; +import org.spearce.jgit.lib.FileCopier; +import org.spearce.jgit.lib.ObjectDatabase; +import org.spearce.jgit.lib.ObjectId; +import org.spearce.jgit.lib.Tree; + +public class ObjectDatabaseTest extends TestCase { + public void testConnect() throws IOException { + final ObjectDatabase d; + final Commit c; + + d = new ObjectDatabase(new File( + "/Users/spearce/cgwork/PlayAreas/test1/.git")); + final String id = "25a4dc8a35d3c288e5eaa366ed95bb0c08507ec7"; + c = d.openCommit(new ObjectId(id)); + assertNotNull(c); + assertEquals(id, c.getCommitId().toString()); + assertNotNull(c.getTreeId()); + assertNotNull(c.getParentIds()); + assertTrue(c.getParentIds().size() > 0); + assertNotNull(c.getAuthor()); + assertNotNull(c.getCommitter()); + + System.out.println("commit=" + c.getCommitId()); + System.out.println("tree=" + c.getTreeId()); + System.out.println("parents=" + c.getParentIds()); + System.out.println("author=" + c.getAuthor()); + System.out.println("committer=" + c.getCommitter()); + System.out.println("message={" + c.getMessage() + "}"); + + final Tree t = d.openTree(c.getTreeId()); + assertNotNull(t); + assertEquals(c.getTreeId(), t.getTreeId()); + assertNotNull(t.getTreeEntries()); + printTree(t); + } + + private void printTree(final Tree t) throws IOException { + final Iterator i = t.getTreeEntries().iterator(); + while (i.hasNext()) { + final Tree.Entry e = (Tree.Entry) i.next(); + System.out.println(e); + if (e.isTree()) { + printTree(e.getTree()); + } + } + } + + public void testReadHEAD() throws IOException { + final ObjectDatabase d; + final Commit c; + + d = new ObjectDatabase(new File( + "/Users/spearce/cgwork/PlayAreas/test1/.git")); + final ObjectId id = d.resolveRevision("HEAD"); + c = d.openCommit(id); + assertNotNull(c); + assertEquals(id, c.getCommitId()); + assertNotNull(c.getTreeId()); + assertNotNull(c.getParentIds()); + assertTrue(c.getParentIds().size() > 0); + assertNotNull(c.getAuthor()); + assertNotNull(c.getCommitter()); + + System.out.println("commit=" + c.getCommitId()); + System.out.println("tree=" + c.getTreeId()); + System.out.println("parents=" + c.getParentIds()); + System.out.println("author=" + c.getAuthor()); + System.out.println("committer=" + c.getCommitter()); + System.out.println("message={" + c.getMessage() + "}"); + } + + public void testCopyOut() throws IOException { + final ObjectDatabase d; + final FileCopier c; + + d = new ObjectDatabase(new File( + "/Users/spearce/cgwork/PlayAreas/test1/.git")); + c = new FileCopier(); + final String id = "facb516c64c3ab5729a457b2f2aa42f7d6feafd1"; + final Tree t = d.openTree(new ObjectId(id)); + c.copyOut(t, new File("/Users/spearce/cgwork/PlayAreas/Eclipse1")); + } +} diff --git a/tst/org/spearce/jgit/lib_tst/ObjectIdTest.java b/tst/org/spearce/jgit/lib_tst/ObjectIdTest.java new file mode 100644 index 00000000..08c10097 --- /dev/null +++ b/tst/org/spearce/jgit/lib_tst/ObjectIdTest.java @@ -0,0 +1,27 @@ +package org.spearce.jgit.lib_tst; + +import junit.framework.TestCase; + +import org.spearce.jgit.lib.ObjectId; + +public class ObjectIdTest extends TestCase { + public void test1() { + final String x = "def4c620bc3713bb1bb26b808ec9312548e73946"; + final ObjectId oid = new ObjectId(x); + assertEquals(x, oid.toString()); + } + + public void test2() { + final String x = "ff00eedd003713bb1bb26b808ec9312548e73946"; + final ObjectId oid = new ObjectId(x); + assertEquals(x, oid.toString()); + } + + public void testEquals() { + final String x = "def4c620bc3713bb1bb26b808ec9312548e73946"; + final ObjectId a = new ObjectId(x); + final ObjectId b = new ObjectId(x); + assertEquals(a.hashCode(), b.hashCode()); + assertTrue(a.equals(b)); + } +} -- 2.11.4.GIT