TL; DR
git rev-list
- это обход графа коммитов (DAG). Вы даете это отправные точки. --branches
- это один из способов дать ему различные отправные точки; --remotes
это другое. В отличие от git log
, который по умолчанию использует HEAD
в качестве начальной точки, вы должны дать некоторую фиксацию начальной точки для git rev-list
. Затем в нем перечисляются достижимые коммиты: имена актуальны только с точки зрения определения местоположения начальных точек для обхода графа.
Long
В Git вы должны помнить, что такое ветвь (или нет). Без этой концепции вы сильно заблуждаетесь. Для сравнения давайте сначала рассмотрим Mercurial, где branch - более конкретная вещь (хотя свободное и небрежное определение Git branch в наши дни также просачивается в использование Mercurial).
В Mercurial вы создаете ветку, используя:
hg branch <name>
и затем сделайте коммиты на нем. Эти коммиты на этой ветви, навсегда (ну, до тех пор, пока они не будут лишены , но если игнорировать возможность удалять коммиты, они всегда там). Если commit C
находится на ветке X
на X
, а не на любой другой ветке. Спросите, какие коммиты находятся на ветке X
, и она всегда будет включать C
. Имеет смысл, верно? Комитеты сшиваются навсегда в их филиал; ветвь - это коллекция всех коммитов, когда-либо сделанных в этой ветке.
В Git это не так. Вы делаете коммит C
на ветке X
, а он на ветке X
, но через несколько минут (или дней или месяцев) он также на ветках Y
и Z
и master
. На этом этапе полностью удалите ветку X
, и коммит C
все еще там, все еще в ветвях Y
и Z
и master
. Вы можете даже переместить имя X
, чтобы фиксация C
была , а не на X
больше, но все еще на master
.
Другими словами, в Git коммиты не прикреплены к их ветвям. То, находится ли коммит в какой-либо ветви, является динамическим свойством, которое может измениться в будущем. Коммиты существуют с или без веток, содержащих их. коммиты имеют значение; ветки только для слабых людей. 1 Но коммиты постоянны (ну, в основном) и доступны только для чтения, замороженные навсегда. Так что коммиты исправлены; это имена ветвей , которые перемещаются.
Имея это в виду, давайте нарисуем несколько графиков.
1 Имена ветвей или другие ссылки служат в Git нескольким другим целям, но лучше начать с этого как своей модели. Как только вы поймете эту концепцию, все остальное станет на свои места.
Git commit graphs
Каждый коммит записывает своего непосредственного предшественника или parent , commit (s). В простейшем случае один коммит имеет одного родителя, а родитель этого коммита имеет одного родителя и т. Д. Начиная с конца цепи, мы можем легко работать в обратном направлении:
A <-B <-C ... <-G <-H <--master
Ветка name , в данном случае master
, хранит необработанный хэш-идентификатор last commit, который должен считаться частью этой ветви. В коммите H
хранится еще один хэш-идентификатор коммита для его коммита-предшественника G
. G
хранит хеш-идентификатор своего родителя и т. Д. Обратно через commit B
. В коммите B
хранится хэш-идентификатор A
, но A
- самый первый коммит в истории, поэтому у него нет родителя.
(Поскольку сами коммиты исправлены, больше нет необходимости рисовать внутренние стрелки в виде стрелок, что помогает в следующем шаге, потому что рисование стрелок в тексте довольно ограничено. Поэтому теперь я буду использовать:
A--B--C <-- master
например. Однако стрелки с названиями ветвей много двигаются, поэтому полезно сохранять их в виде стрелок.)
Добавление нового коммита в ветку в Git - это вопрос замораживания исходного снимка и создания объекта коммита. Новый объект фиксации записывает текущий хеш-идентификатор фиксации, поэтому мы поддерживаем эту обратную цепочку. Новый коммит затем получает свой новый уникальный хэш-идентификатор, и Git записывает новый хэш-идентификатор в имя ветви:
A--B--C <-- master
становится:
A--B--C--D <-- master
Но мы можем добавить больше названий веток. Прежде чем мы сделаем еще один новый коммит, давайте сделаем это: давайте сделаем имя ветви develop
также , указывающее на коммит D
:
A--B--C--D <-- master, develop
Теперь Git нужно знать , какое имя ветви для обновления, поэтому Git присоединяет имя HEAD
к одному (и только одному) имени ветви:
A--B--C--D <-- master, develop (HEAD)
Теперь, когда мы делаем новый коммит E
, имя ветви, которое обновляет Git, больше не master
, а скорее develop
:
A--B--C--D <-- master
\
E <-- develop (HEAD)
фиксирует E
сейчас на develop
. Коммиты A
и D
также находятся в разработке и находятся на master
. Давайте git checkout master
сейчас и сделаем новый коммит F
:
A--B--C--D--F <-- master (HEAD)
\
E <-- develop
Commit F
теперь only on master
, в то время как E
is only on develop
, а A-B-C-D
оба.
Если мы сделаем еще несколько коммитов при разработке, мы получим:
A--B--C--D--F <-- master
\
E--G--H <-- develop
(я пропустил HEAD
здесь, так как мы все равно собираемся его переместить.)
Теперь давайте запустим git checkout master && git merge develop
. Это объединит все наши master
изменения с момента общего коммита D
со всеми нашими develop
изменениями с момента общего коммита D
. То есть Git будет работать:
git diff --find-renames <hash-of-D> <hash-of-E> # what happened on master
git diff --find-renmaes <hash-of-D> <hash-of-H> # what happened on develop
Git объединит два набора изменений, применяет их к снимку в D
и сделает новый коммит I
, имеющий двух родителей:
A--B--C--D--F------I <-- master (HEAD)
\ /
E--G--H <-- develop
Commit I
возвращается к и H
и F
, а H
возвращается к G
, затем к E
, а затем сделанная нами вилка возвращается в обратном направлении на D
, что приводит к C
и B
и, наконец, A
. Итак, все эти коммиты теперь включены master
. Коммиты I
и F
являются только на master
, в то время как A-B-C-D-E-G-H
находятся на обеих ветвях.
Это особенность коммита слияния. Теперь, когда у нас есть коммит слияния, мы можем удалить имя develop
:
A--B--C--D--F------I <-- master (HEAD)
\ /
E--G--H
Все коммиты остаются на master
. Имя develop
больше не существует. Это было когда-нибудь, или это был мираж? Git в любом случае не волнует.
git rev-list
и git log
И git rev-list
, и git log
связаны с этим графом коммитов. Эти две команды довольно близки друг к другу - на самом деле они построены из одного и того же исходного файла, с двумя разными точками входа в зависимости от того, выполняли ли вы git log
или git rev-list
. Оба будут проходить по графику, начиная с заданных вами концов и работая в обратном направлении. В то время как git log
по умолчанию показывает каждый пройденный коммит, git rev-list
по умолчанию просто печатает их хэш-идентификаторы.
Другое ключевое отличие - это HEAD
понятие. Git хочет отслеживать, какую ветку вы извлекли, прикрепив имя HEAD
к одному имени ветви. Команда git log
по умолчанию ищет HEAD
и использует ее в качестве своей (единственной) начальной точки; git rev-list
требует, чтобы вы дали ему некоторую отправную точку (и).
Они оба делают это "начни с конца и работай задом наперед". Если master
указывает на коммит I
, как в нашем примере выше, и I
является коммитом слияния, который указывает на коммиты F
и H
, оба сначала покажут, что вы зафиксировали I
(или его хеш ID), затем выберите один из F
и H
, чтобы отобразить следующий. Затем они покажут G
и E
(и F
, если они еще этого не сделали, где-то в этом миксе), а D
и C and
B and
A , and since
A` не имеют родителя , остановись там.
В конце оба будут показывать или перечислять каждый коммит, поскольку каждый коммит достижим с заданной начальной точки, коммит I
. Если вы дадите им другую отправную точку, такую как commit D
, они покажут вам D
, C
, B
, A
.
.
Шаблоны (с --branches=*
) могут быть хитрыми, потому что интерпретаторы командной строки ("оболочки") имеют тенденцию делать свое собственное расширение *
.Если вы запустите echo *
, вы увидите список всех имен ваших файлов.Если вы запустите echo foo*
, вы увидите только имена файлов, начинающиеся с foo
.--branches=*
может попытаться развернуть файлы, имена которых начинаются с --branches=
, особенно если у вас есть файлы с этими именами.
Если вы опустите *
в конце, Git предоставит его внутренне, чтоизбегает снарядов.