Я недавно прочитал книгу 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 c03265971361724e18e31cc83e5c60cd0e0f5754 parent 141f5d5a2cc0c268e7b05be17a49c1c0dc61efad author Johannes Brodwall 1379445359 +0200 committer Johannes Brodwall 1379445359 +0200 This 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.