Git не имеет «историю файлов». Git имеет коммиты, а коммиты являются историей, потому что каждый коммит имеет некоторый набор родительских коммитов . Когда мы - или Git - связываем коммит с его родителем (-ями), а затем используем его / ее родитель (ы) для связи с другими родителями, мы получаем график:
A <-B <-C <--master
\
D <--develop
Здесь имя master
выбирает коммит C
, чьим родителем является коммит B
. Имя develop
выбирает коммит D
, родитель которого также является коммитом B
. Коммит B
имеет коммит A
в качестве родителя, и поскольку этот репозиторий настолько мал, коммит A
является самым первым коммитом и вообще не имеет родителя.
Следовательно, история в этом хранилище такова, что C
ведет к B
, что приводит к A
, а D
ведет к B
, который мы уже видели. Это это история. Это все есть ... кроме ...
Хорошо, мы знаем, что каждый коммит представляет собой снимок всех файлов. Так что commit C
содержит некоторый набор файлов. Зафиксировать B
также имеет снимок всех файлов. Если набор файлов в коммите C
совпадает с набором файлов в коммите B
, за исключением одного конкретного файла, такого как README
, который отличается, почему тогда мы можем синтезировать a история для файла README
: она была изменена в C
относительно его родителя B
.
Файл просто там , в C
и B
, но в этих двух коммитах он отличается . Два коммита связаны - B
является родителем C
(что означает, что C
является дочерним элементом B
, хотя нам нужно вычислить , поскольку Git хранит только обратные ссылки ). Таким образом, это позволяет нам синтезировать историю файлов: мы смотрим историю фиксации и извлекаем информацию о файле, который нас интересует. Если он изменил от одного дочернего элемента до своего родителя, мы можем утверждать, что это интересное изменение, записывающее идентификаторы дочернего и родительского идентификатора (и если у дочернего элемента только один родитель, нам даже не нужно записывать родительский идентификатор).
Добавление нового коммита просто добавляет больше истории
Предположим, у нас есть вышеуказанный репозиторий с четырьмя коммитами, и мы его клонируем, чтобы мы могли работать над ним. В нашем клоне мы извлекаем ветку develop
, так что commit D
является текущим коммитом, а наш HEAD
присоединяется к имени develop
:
A--B--C <-- master
\
D <-- develop (HEAD)
Теперь мы редактируем файл README
, запускаем git add README
и запускаем git commit
. Наш Git делает новый коммит E
, который получает новый, уникальный, большой уродливый хеш-идентификатор и сохраняет его в нашем репозитории. Он устанавливает parent из E
в D
- фиксацию, которая была текущей, когда мы запустили git commit
- и затем сохраняет хэш-идентификатор E
в нашем имени develop
, давая нам :
A--B--C <-- master
\
D--E <-- develop (HEAD)
Наше имя develop
теперь указывает на фиксацию E
вместо фиксации D
.
Если мы теперь хотим изучить историю файла README
, мы должны начать с коммитов E
и commit C
и работать в обратном направлении. README
изменился с E
на D
? Да, так что commit E
(parent D
) интересен. README
изменился с C
на B
? Да, так что commit C
(parent B
) интересен. Мы должны повторить это для каждого коммита в этом хранилище, и это дает нам синтезированную историю файла README
.
О "вилках"
Разветвление - это просто клон какого-то другого хранилища, но сделанный с каким-то намерением , обычно для передачи изменений обратно в исходное хранилище и / или для получения изменений из исходного хранилища. Для этого вилка содержит ссылку на исходный репозиторий, так же, как любой клон обычно содержит ссылку на свой исходный репозиторий.
В св одиночку, эта ссылка на оригинал имеет имя , которое Git называет remote . Стандартное название origin
. Git использует это, чтобы автоматически получать новые коммиты из исходного репозитория, используя git fetch
, и отправлять новые коммиты, сделанные в клоне, в исходный репозиторий, используя git push
. Шаг выборки выбирает коммиты, сделанные с момента последней выборки или с момента первоначального клона. То есть у них есть коммиты, которых у вас нет. Шаг толчка дает им коммиты, которые у вас есть, а они нет. Опять же, мы видим, что Git имеет и заботится о коммитах.
Когда вы используете кнопку веб-страницы "fork a repository" веб-сервера, сам сервер записывает, каким-то закулисным способом, ответвление. Можно ли и как получить этих записей, чтобы найти соответствующие URL-адреса сервера для каждого такого клона, зависит от сервера. Если вы можете найти их все, вы можете просто добавить URL-адрес к существующему клону, одно дополнительное имя для каждого URL-адреса, а затем использовать git fetch --all
для извлечения из всех пультов ( все серверные клоны) в один клон.
Истинное имя коммита - его хэш-идентификатор
Во вселенной Git каждый коммит имеет уникальный хэш-идентификатор, отличный от любого другого коммита. Два репозитория Git решают, есть ли в одном из них коммит, а в другом нет, путем сравнения хеш-идентификаторов, поэтому эти должны быть уникальными. Не вдаваясь в подробности, это действительно работает: два коммита одинаковы, если их хэши совпадают, и разные, если нет. Это означает, что независимо от того, сколько времени прошло с тех пор, как два хранилища разошлись, если в какой-то момент они совместно использовали некоторый набор коммитов в силу того, что один был клонирован из другого, они будут делиться некоторыми коммитами. 1 Истории подключиться в этой точке. Все уникальные последующие коммиты, конечно, будут уникальными, хотя, если кто-то, кто сделал форк (т.е. клон), каким-то образом перенес этот коммит обратно на один из других форков (т. Е. На другой клон), эти два клона будут совместно использовать фиксированный возврат.
1 Существует предупреждение: предполагается, что никто не "переписывает историю", копируя каждый коммит в новый, другой коммит с другим хешем ID, затем перестал использовать все оригиналы. В этом случае два хранилища больше не разделяют никаких коммитов, и истории не будут соединяться.
Каждый клон является полным и независимым от всех других клонов, за исключением случаев, когда вы их перекрестно соединяете
Хотя верно, что любой коммит, чей хэш-идентификатор совпадает с одинаковым во всех клонах (по определению), каждый клон не зависит от каждого другого клона всегда. Под этим мы подразумеваем, что тот, кто управляет этим клоном, может добавить к нему новые коммиты или изменить имена его ветвей, чтобы эти имена ссылались на разные коммиты. Например, после того, как мы добавим коммит E
в наш репозиторий, мы можем удалить коммит E
снова, используя git reset
. Если мы сделаем это, и не никуда не продвинет наш новый коммит E
, никто больше никогда не увидит, что мы его сделали. Изменение, которое мы внесли в README
в коммите E
(относительно его родителя D
), исчезает:
A--B--C <-- master
\
D <-- develop (HEAD)
\
E [abandoned]
Это особенность распределенных репозиториев. Коммиты существуют только там, где они существуют - тавтологические, но истинные. Когда коммит отправляется из нашего клона в какой-либо другой репозиторий, коммит существует в двух репозиториях. Его уникальный идентификатор хэша теперь в обоих. Единственный способ избавиться от него сейчас - убедиться, что в обоих репозиториях есть имена, в которых найти коммит E
, так что эти имена больше не находят коммит E
. Обычно мы не пытаемся удалить коммиты, если они не были переданы кому-либо еще, , хотя, поскольку для этого требуется , перейдите к каждому клону, которому мы дали коммит, и убедитесь, что они тоже отбрасывают этот коммит действие. Следовательно, после публикации в общедоступном клоне, таком как веб-форк, коммиты становятся постоянными.
Git в общем очень липкий с коммитами. Если мы кросс-соединяем наш клон с коммитом E
в нем (достижимым от имени develop
) к другому клону, и имеем другой Git делает выборку, его Git спросит обо всех наших коммитах, которых у них нет, и возьмет новый коммит и даст ему имя типа ourclone/develop
. Таким образом, git fetch
-ing с каждого форка будет собирать каждый коммит, доступный в каждом репозитории, давая нам своего рода супер-набор клонов.
Этот супер-набор клонов позволит вам делать то, что вы хотите
Как только у нас есть супер-набор, в котором собраны все вилки, мы можем заставить наш Git находить каждый интересный коммит, начиная с каждого имени для удаленного отслеживания (forkA/master
, forkA/develop
, forkB/master
и т. Д. ), ищет дочерние коммиты с README
, который отличается от их родительских коммитов. Поскольку хэш-идентификаторы универсальны, теперь мы можем, глядя на любой отдельный форк, сказать, есть ли у этого форка конкретная версия этого файла. Но нам нужно построить довольно массивную историю комбинированных коммитов, чтобы точно видеть , где каждый экземпляр этого файла находится в фактической истории, которая является набором всех коммитов, потому что файлы не имеют истории.
Обратите внимание, что на самом деле вам не нужно создавать супер-набор клонов - но вам делать нужен доступ к каждому коммиту по его хеш-идентификатору, чтобы увидеть, какая версия README
является в , которые фиксируют, и посмотрите, что является (являются) родителем (ями) этого коммита, и какая версия README
находится в родителе (ях). Это означает, что вы должны иметь доступ к каждому форку и видеть все коммиты на этом форке. Работа, которую вы выполняете для создания всей этой информации, - это такая же , как и работа, которую вы выполняете, чтобы построить супер-набор клонов, так что вы также можете построить супер-набор клонов.