Я недавно прочитал книгу 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. Этот файл является сжатым файлом, содержащим информацию о коммите. Вот код для чтения:
|
01
02
03
04
05
06
07
08
09
10
|
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));} |
Запуск этого кода приводит к следующему выводу (обратите внимание, что некоторые пробелы в выводе на самом деле являются нулевыми байтами в файле):
|
1
2
3
4
5
6
|
commit 237 tree c03265971361724e18e31cc83e5c60cd0e0f5754parent 141f5d5a2cc0c268e7b05be17a49c1c0dc61efadauthor Johannes Brodwall 1379445359 +0200committer Johannes Brodwall 1379445359 +0200This is the commit comment |
Нахождение дерева каталогов коммита
Когда у нас есть информация о коммите, мы можем проанализировать ее, чтобы найти хэш дерева . Хеш дерева ссылается на другой файл в .git / objects, который содержит индекс корневого каталога файлов в коммите. В приведенном выше примере хеш дерева «c03265971361724e18e31cc83e5c60cd0e0f5754». Но прежде чем читать хеш дерева, мы должны прочитать тип объекта (в данном случае «коммит») и размер (в данном случае 237).
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
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));} |
Однако посмотреть на хеш-файл дерева не так просто:
|
1
2
3
|
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). После этого он перечислит каждый файл или каталог. Каждая запись дерева состоит из прав (которые также сообщают нам, является ли это файл или каталог), имени файла и хеша записи, но на этот раз в виде двоичного числа . Мы можем прочитать записи и найти нужный файл. Затем мы можем просто распечатать содержимое этого файла:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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) и файлами:
|
1
2
3
4
|
{FOO.txt=fd6385d5f4b9ec6decaa47416b7f96588aef2726,lib=8ff147bbd18fbc3e2638b42ea09cfacba2695b6f,README=57fd19a7738eba1e79e2782b161a40ee52b05801,FOO=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391} |
Чтение файла
Это приводит нас к концу нашего путешествия — как читать содержимое файла. Как только у нас есть записи дерева, нужно просто найти хеш для имени файла и проанализировать этот файл. Как и прежде, содержимое файла будет начинаться с типа («blob» — что означает «данные», я думаю) и размера файла:
|
1
2
3
4
5
6
7
8
|
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, проанализировать список файлов корня дерева файлов для этого коммита и распечатайте содержимое файла. Самым сложным было выяснить, что для распаковки git-объекта нужен был InflaterInputStream а не Zip или Gzip.
Мой проект silly-jgit поддерживает чтение и запись коммитов , деревьев и хэшей из .git / objects. Это только основной поднабор команд Git. Более того, когда я писал статью, я заметил, что git часто упаковывает объекты в .git / objects / pack. Это добавляет совершенно новое измерение, с которым я раньше не сталкивался.
Я надеюсь, что никто не настолько безумен, чтобы использовать мою глупую библиотеку Git для Java. Но я очень надеюсь, что эта статья дала вам ощущение мастерства в Git.