-
Notifications
You must be signed in to change notification settings - Fork 1
Git_Pointers
В определенной степени работа с Git сводится к работе с графом истории и перемещению указателей. Речь пойдет о ветках/ссылках в Git.
(Часть коммитов сгенерирована рандомно).
Для начала создадим пустой репозиторий. (Тестовый репо на https://github.com/aplb/git-pointers.git)
mkdir git-pointers
cd git-pointers
git init
Содержимое .git/ имеет следующий вид (каталог .git/hooks/ предварительно удален):
.git/
├── branches
├── config
├── description
├── HEAD
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
Далее нас будут интересовать refs/heads и HEAD.
cat .git/HEAD
ref: refs/heads/master
По умолчанию HEAD ссылается на ветку master (при этом, refs/heads/master еще нет).
Поскольку коммиты не сделаны, попытка посмотреть историю командой git-log
приводит к ошибке
(fatal: bad default revision 'HEAD')
(из файла revision.c die("bad default revision '%s'", revs->def);)
Предположим, я создал два файла arbitrary.txt и password.txt с произвольным текстом и добавил их в индекс. Обратим внимание, что в директории .git/objects появилось 2 новых объекта.
.git/
├── branches
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── objects
│ ├── 43
│ │ └── 99a58404431b72c3f91a0fc8567883beb007af
│ ├── 9d
│ │ └── 84fae31b1d7b692acc454896166b37f7879bf4
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
Служебная команда git-cat-file
подтверждает, что с помощью sha1-hash закодировано содержимое
созданных файлов и являются они blob-ами (один из типов объектов в Git).
git cat-file -t 4399a58404431b72c3f91a0fc8567883beb007af
blob
git cat-file -t 9d84fae31b1d7b692acc454896166b37f7879bf4
blob
а
git cat-file -p 4399a58404431b72c3f91a0fc8567883beb007af
some text here
git cat-file -p 9d84fae31b1d7b692acc454896166b37f7879bf4
5 stars *****
Собираясь сделать первый коммит, вместо git commit -m
, я воспользуюсь двумя plumbing командами, чтобы
продемонстрировать, каким образом в Git происходит обновление ссылок.
- В первую очередь необходимо создать дерево из текущего индекса.
git write-tree
1fc4773412b807079495823801ffe1db8023d6f8
Видим ещё один объект в .git/objects, который является деревом.
├── objects
│ ├── 1f
│ │ └── c4773412b807079495823801ffe1db8023d6f8
│ ├── 43
│ │ └── 99a58404431b72c3f91a0fc8567883beb007af
│ ├── 9d
│ │ └── 84fae31b1d7b692acc454896166b37f7879bf4
Перепроверим тип этого объекта:
git cat-file -t 1fc4773412b807079495823801ffe1db8023d6f8
tree
В это дерево входят добавленные чуть ранее в индекс 2 файла.
git cat-file -p 1fc4773412b807079495823801ffe1db8023d6f8
100644 blob 4399a58404431b72c3f91a0fc8567883beb007af arbitrary.txt
100644 blob 9d84fae31b1d7b692acc454896166b37f7879bf4 password.txt
- После того, как мы создали дерево, которое содержит snapshot состояния проекта на определенный момент, можно смело его зафиксировать посредством еще одной служебной команды commit-tree.
git commit-tree 1fc4773412b807079495823801ffe1db8023d6f8 -m 'initial commit'
95188b2efd42b1390f51337e7f2b72e4d49744ca
В .git/objects появился 4-й объект.
├── objects
│ ├── 1f
│ │ └── c4773412b807079495823801ffe1db8023d6f8
│ ├── 43
│ │ └── 99a58404431b72c3f91a0fc8567883beb007af
│ ├── 95
│ │ └── 188b2efd42b1390f51337e7f2b72e4d49744ca
│ ├── 9d
│ │ └── 84fae31b1d7b692acc454896166b37f7879bf4
Снова проверим тип нового объекта:
git cat-file -t 95188b2efd42b1390f51337e7f2b72e4d49744ca
commit
это действительно коммит;
отобразить его содержимое:
git cat-file -p 95188b2efd42b1390f51337e7f2b72e4d49744ca
tree 1fc4773412b807079495823801ffe1db8023d6f8
author aplb <[email protected]> 1396863114 +0300
committer aplb <xxxxxx> 1396863114 +0300
initial commit
Также информацию об этом коммите можно получить с помощью
git show 95188b2efd42b1390f51337e7f2b72e4d49744ca --pretty=raw
Казалось бы, коммит сделан. Но у нас все еще нет истории, команда git-log
по-прежнему завершается
сообщением об ошибке.
Довести высокоуровневый git commit
до конца поможет обновление ссылок.
git update-ref refs/heads/master 95188b2efd42b1390f51337e7f2b72e4d49744ca
(а можно просто git update-ref HEAD 95188b2efd42b1390f51337e7f2b72e4d49744ca, так как HEAD ссылается на master, см. cat .git/HEAD)
Теперь в нашем репозитории создана история изменений.
Далее я сделал несколько коммитов, история изменений приняла следующий вид.
┏ 95188b2 (49 minutes ago) aplb initial commit
┣ 2c6b8bd (3 seconds ago) aplb A random change of 26097 to foo1.txt
┣ 334b363 (3 seconds ago) aplb A random change of 27179 to foo2.txt
┣ c34c860 (3 seconds ago) aplb A random change of 4498 to foo3.txt
┗━[HEAD]──[master]──797c515 (3 seconds ago) aplb A random change of 17368 to foo4.txt
HEAD ссылается на ветку master, а master указывает на коммит, который был сделан последним.
cat .git/refs/heads/master
797c515ade1de8e2281889371101ee8219e86098
Сейчас можно сделать историю чуть более изощренной. Допустим, нам понадобилось внести ряд изменений в отдельной ветке friday-fix. При этом в ней не нужны изменения последних трех коммитов ветки master. Создадим новую ветку, берущую начало от коммита 2c6b8bd.
git checkout -b friday-fix 2c6b8bd
┏ 95188b2 (58 minutes ago) aplb initial commit
┣━[HEAD]──[friday-fix]──2c6b8bd (9 minutes ago) aplb A random change of 26097 to foo1.txt
┣ 334b363 (9 minutes ago) aplb A random change of 27179 to foo2.txt
┣ c34c860 (9 minutes ago) aplb A random change of 4498 to foo3.txt
┗━[master]──797c515 (9 minutes ago) aplb A random change of 17368 to foo4.txt
Так, мы просто создали новый указатель refs/heads/friday-fix, который ссылается на коммит 2c6b8bd,
cat .git/refs/heads/friday-fix
2c6b8bd92e060ab5e84c05bf2812719ca40f1ce3
а HEAD теперь будет указывать на friday-fix (пока мы не переместимся с помощью checkout).
cat .git/HEAD
ref: refs/heads/friday-fix
В новой ветке я сделал несколько коммитов. История разветвилась.
Можно быстро перемещаться с ветки master на friday-fix и обратно, используя git-checkout
, при этом
обновляться будет указатель HEAD.
Вот так можно видеть, на какую ветку указывает HEAD.
┏ 95188b2 (63 minutes ago) aplb initial commit
┣ 2c6b8bd (13 minutes ago) aplb A random change of 26097 to foo1.txt
┣━┓
┃ ┣ 334b363 (13 minutes ago) aplb A random change of 27179 to foo2.txt
┃ ┣ c34c860 (13 minutes ago) aplb A random change of 4498 to foo3.txt
┃ ┗━[master]──797c515 (13 minutes ago) aplb A random change of 17368 to foo4.txt
┣ fa74f9f (4 seconds ago) aplb A random change of 31507 to essentials1.txt
┣ dc65ae7 (4 seconds ago) aplb A random change of 4288 to essentials2.txt
┗━[HEAD]──[friday-fix]──9fc036b (4 seconds ago) aplb A random change of 31771 to essentials3.txt
Однако хотелось бы привести несколько вычурный пример, который покажет силу указателей в Git.
Итак, я выполнил несколько перемещений между ветками master и friday-fix. Воспользуемся командой
git reflog
все изменения HEAD зафиксированы.
9fc036b HEAD@{0}: checkout: moving from master to friday-fix
797c515 HEAD@{1}: checkout: moving from friday-fix to master
9fc036b HEAD@{2}: commit: A random change of 31771 to essentials3.txt
dc65ae7 HEAD@{3}: commit: A random change of 4288 to essentials2.txt
fa74f9f HEAD@{4}: commit: A random change of 31507 to essentials1.txt
2c6b8bd HEAD@{5}: checkout: moving from master to friday-fix
797c515 HEAD@{6}: commit: A random change of 17368 to foo4.txt
c34c860 HEAD@{7}: commit: A random change of 4498 to foo3.txt
334b363 HEAD@{8}: commit: A random change of 27179 to foo2.txt
2c6b8bd HEAD@{9}: commit: A random change of 26097 to foo1.txt
95188b2
История по-прежнему в порядке. Заметим, ветка master содержит 5 коммитов.
┏ 95188b2 (68 minutes ago) aplb initial commit
┣ 2c6b8bd (19 minutes ago) aplb A random change of 26097 to foo1.txt
┣━┓
┃ ┣ 334b363 (19 minutes ago) aplb A random change of 27179 to foo2.txt
┃ ┣ c34c860 (19 minutes ago) aplb A random change of 4498 to foo3.txt
┃ ┗━[master]──797c515 (19 minutes ago) aplb A random change of 17368 to foo4.txt
┣ fa74f9f (6 minutes ago) aplb A random change of 31507 to essentials1.txt
┣ dc65ae7 (6 minutes ago) aplb A random change of 4288 to essentials2.txt
┗━[HEAD]──[friday-fix]──9fc036b (6 minutes ago) aplb A random change of 31771 to essentials3.txt
В этот момент я, пытаясь продемонстрировать работу git-update-ref
, по ошибке выполняю
git update-ref refs/heads/master fa74f9f
Дерево истории:
┏ 95188b2 (72 minutes ago) aplb initial commit
┣ 2c6b8bd (23 minutes ago) aplb A random change of 26097 to foo1.txt
┣━[master]──fa74f9f (9 minutes ago) aplb A random change of 31507 to essentials1.txt
┣ dc65ae7 (9 minutes ago) aplb A random change of 4288 to essentials2.txt
┗━[HEAD]──[friday-fix]──9fc036b (9 minutes ago) aplb A random change of 31771 to essentials3.txt
Git сделал именно то, о чем я его попросил. Указатель (ветка) master теперь ссылается на один из коммитов ветки friday-fix (см. cat .git/refs/heads/master). В ветке master всего 3 коммита, вместо 5. При этом работа из трех коммитов master-ской ветки потеряна. Вот они:
┣ 334b363 (19 minutes ago) aplb A random change of 27179 to foo2.txt
┣ c34c860 (19 minutes ago) aplb A random change of 4498 to foo3.txt
┗ 797c515 (19 minutes ago) aplb A random change of 17368 to foo4.txt
И здесь может помочь git-reflog
. Всё, что нужно, это sha коммита, куда мы хотим вернуться:
797c515 (19 minutes ago) aplb A random change of 17368 to foo4.txt
git update-ref refs/heads/master 797c515
(или git update-ref refs/heads/master HEAD@{6})
Искомое состояние истории восстановлено, master указывает на верхушку прежней ветки.
┏ 95188b2 (78 minutes ago) aplb initial commit
┣ 2c6b8bd (29 minutes ago) aplb A random change of 26097 to foo1.txt
┣━┓
┃ ┣ 334b363 (29 minutes ago) aplb A random change of 27179 to foo2.txt
┃ ┣ c34c860 (29 minutes ago) aplb A random change of 4498 to foo3.txt
┃ ┗━[master]──797c515 (29 minutes ago) aplb A random change of 17368 to foo4.txt
┣ fa74f9f (16 minutes ago) aplb A random change of 31507 to essentials1.txt
┣ dc65ae7 (16 minutes ago) aplb A random change of 4288 to essentials2.txt
┗━[HEAD]──[friday-fix]──9fc036b (16 minutes ago) aplb A random change of 31771 to essentials3.txt
Вот и всё. Переключаться между ветками, искать потерянные изменения и вручную обновлять ссылки в Git просто.