Я недавно прочитал книгу Git . Когда я проходил через части Git Internals, меня поразило, насколько проста и элегантна структура Git на самом деле. Я решил, что мне просто нужно создать свою маленькую библиотеку для работы с репозиториями Git (как и вы). Я называю результат Silly Jgit . В этой статье я буду идти по коду.
Эта статья для вас, если вы хотите немного глубже понять Git или, возможно, даже хотите работать непосредственно с Git-репозиторием на вашем любимом языке программирования. Я пройдусь по четырем темам: 1) Чтение необработанного коммита из репозитория, 2) Чтение хеша дерева корня коммита, 3) Анализ списка файлов дерева каталогов и 4) Чтение содержимого файла из подкаталог коммит корня.
Чтение головы коммит из репозитория
Первое, что нам нужно сделать, чтобы прочитать заголовок коммита, это выяснить, какой коммит является главой репозитория. Файл .git / HEAD представляет собой простой текстовый файл, который содержит имя файла в каталоге .git / refs /head. Если вы проверили мастер, это будет .git / refs /head / master. Этот файл представляет собой простой текстовый файл, который содержит хэш , то есть шестнадцатеричное число из 40 цифр. Хеш может быть преобразован в имя файла Git Object в .git / objects. Этот файл является сжатым файлом, содержащим информацию о коммите. Вот код для чтения:
File repository = new File(".git");
File headFile = new File(repository,
Util.asString(new File(repository, "HEAD")).split(" ")[1].trim());
String commitHash = Util.asString(headFile).trim();
File commitFile = new File(repository,
"objects/" + commitHash.substring(0,2) + "/" + commitHash.substring(2));
try(final InputStream inputStream = new InflaterInputStream(new FileInputStream(commitFile))) {
System.out.println(Util.asString(inputStream));
}
Запуск этого кода приводит к следующему выводу (обратите внимание, что некоторые пробелы в выводе на самом деле являются нулевыми байтами в файле):
commit 237 tree c03265971361724e18e31cc83e5c60cd0e0f5754 parent 141f5d5a2cc0c268e7b05be17a49c1c0dc61efad author Johannes Brodwall 1379445359 +0200 committer Johannes Brodwall 1379445359 +0200 This is the commit comment
Нахождение дерева каталогов коммита
Когда у нас есть информация о коммите, мы можем проанализировать ее, чтобы найти хэш дерева . Хеш дерева ссылается на другой файл в .git / objects, который содержит индекс корневого каталога файлов в коммите. В приведенном выше примере хеш дерева «c03265971361724e18e31cc83e5c60cd0e0f5754». Но прежде чем читать хеш дерева, мы должны прочитать тип объекта (в данном случае «коммит») и размер (в данном случае 237).
String treeHash;
try(final InputStream inputStream = new InflaterInputStream(new FileInputStream(commitFile))) {
String type = Util.stringUntil(inputStream, ' ');
long length = Long.valueOf(Util.stringUntil(inputStream, (char)0));
Util.stringUntil(inputStream, ' ');
treeHash = Util.stringUntil(inputStream, '\n');
System.out.println("Tree hash: " + treeHash);
}
File rootTreeFile = new File(repository,
"objects/" + treeHash.substring(0,2) + "/" + treeHash.substring(2));
try(final InputStream inputStream = new InflaterInputStream(new FileInputStream(rootTreeFile))) {
System.out.println(Util.asString(inputStream));
}
Однако посмотреть на хеш-файл дерева не так просто:
tree 130 100644 FOO æ?â?²ÑÖCK?)®wZØÂä?S? 100644 FOO.txt ýc?Õô¹ìmìªGAk?X?ï'& 100644 README Wýs?ºyâx+@îR°X040000 lib ?ñG»Ñ?¼>&8´. ?úË¢i[o
Следующая часть этой статьи покажет, как с этим бороться.
Разбор дерева каталогов
В дереве есть много мусора. Но не паникуйте. Как и в случае с объектом коммита, объект дерева начинается с типа («дерево») и размера (130). После этого он перечислит каждый файл или каталог. Каждая запись дерева состоит из прав (которые также сообщают нам, является ли это файл или каталог), имени файла и хеша записи, но на этот раз в виде двоичного числа . Мы можем прочитать записи и найти нужный файл. Затем мы можем просто распечатать содержимое этого файла:
File rootTreeFile = new File(repository,
"objects/" + treeHash.substring(0,2) + "/" + treeHash.substring(2));
Map<string ,String> entries = new HashMap<>();
try(final InputStream inputStream = new InflaterInputStream(new FileInputStream(rootTreeFile))) {
String type = Util.stringUntil(inputStream, ' ');
long length = Long.valueOf(Util.stringUntil(inputStream, (char)0));
while (true) {
String octalMode = Util.leftPad(Util.stringUntil(inputStream, ' '), 6, '0');
if (octalMode == null) break;
String path = Util.stringUntil(inputStream, (char)0);
StringBuilder hash = new StringBuilder();
for (int i=0; i<20; i++) {
hash.append(Util.leftPad(Integer.toHexString(inputStream.read()), 2, '0'));
}
entries.put(path, hash.toString());
}
}
System.out.println(entries);
</string>
Вот пример разобранного списка каталогов. Я не показал octalMode для каждого файла, но это может быть чрезвычайно полезно для разделения между каталогами (которые octalMode начинается с 0) и файлами:
{FOO.txt=fd6385d5f4b9ec6decaa47416b7f96588aef2726,
lib=8ff147bbd18fbc3e2638b42ea09cfacba2695b6f,
README=57fd19a7738eba1e79e2782b161a40ee52b05801,
FOO=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391}
Чтение файла
Это приводит нас к концу нашего путешествия — как читать содержимое файла. Как только у нас есть записи дерева, нужно просто найти хеш для имени файла и проанализировать этот файл. Как и прежде, содержимое файла будет начинаться с типа («blob» — что означает «данные», я думаю) и размера файла:
String blobHash = entries.get("README");
File blobFile = new File(repository, "objects/" + blobHash.substring(0,2) + "/" + blobHash.substring(2));
try(final InputStream inputStream = new InflaterInputStream(new FileInputStream(blobFile))) {
String type = Util.stringUntil(inputStream, ' ');
long length = Long.valueOf(Util.stringUntil(inputStream, (char)0));
System.out.println(Util.asString(inputStream));
}
Это печатает содержимое нашего файла. Очевидно, что если вы хотите найти файл в подкаталоге, вам придется проделать немного больше работы: проанализировать другой объект дерева и посмотреть и запись в этом объекте и т. Д.
Выводы
В этом сообщении блога показано, как менее чем за 50 строк кода, без каких-либо зависимостей (но с небольшим вспомогательным классом утилит), мы можем найти главный коммит репозитория git, проанализировать список файлов корня дерева файлов для этого коммита и распечатайте содержимое файла. Самым сложным было выяснить, что InflaterInputStreamдля распаковки git-объекта нужен был не Zip или Gzip, а сам Zip.
Мой проект silly-jgit поддерживает чтение и запись коммитов , деревьев и хэшей из .git / objects. Это только основной поднабор команд Git. Более того, когда я писал статью, я заметил, что git часто упаковывает объекты в .git / objects / pack. Это добавляет совершенно новое измерение, с которым я раньше не сталкивался.
Я надеюсь, что никто не настолько безумен, чтобы использовать мою глупую библиотеку Git для Java. Но я очень надеюсь, что эта статья дала вам ощущение мастерства в Git.