Это очень помогает понять Git, если вы помните следующее:
Git - это всего лишь commits . Ветви - или ветви names - имеют значение только для того, чтобы позволить вам (и Git) найти хэш-идентификаторы. Хеш-идентификаторы - это настоящие имена коммитов. Хеш-идентификаторы выглядят случайными, поэтому нам нужны имена, чтобы найти их. Имя ветви, такое как master
или dev
, содержит один хеш-идентификатор: хеш-код последнего коммита в этой ветке.
Эти хеш-идентификаторы универсальны! Они всегда одинаковы, в каждом Git-хранилище везде. Git-репозиторий либо имеет этот коммит, который имеет этот хэш-идентификатор ... или он вообще не имеет этого коммита. Никакой хэш-идентификатор нельзя использовать повторно для другого коммита. 1
Git называется распределенный , но вы можете подумать об этомлучше как реплицируется . (Технически, однако, распределенное - более подходящее слово. Используйте все, что вам нужно, чтобы держать его прямо в голове.)
Каждый репозиторий имеет (обычно в любом случае) полную копию всехсовершает это когда-либо видел. В первый раз, когда вы git clone
используете какой-либо другой репозиторий, вы получаете полную копию всех его коммитов. После этого, однако, два клона могут разойтись, , за исключением , когда вы делаете один вызов другого.
Клоны общаются только друг с другом, когда вы соединяете их- если один репозиторий вызывает другой, обычно через https://...
или ssh://...
URL, но обычно вы скрываете этот URL под простым именем, таким как origin
.
Соединение выполняется с помощью git fetch
(«получить коммиты») и git push
(«дать коммиты»). Команда git pull
здесь отвлекает: на самом деле это просто означает run git fetch
, затем выполните вторую команду Git. Это часть git fetch
, которая заставляет ваш Git общаться с другим Git.
Итак, чтобы обновить вашего клона чем-нибудь новым они были получены или сделаны, вы запускаете git fetch
. Ваш Git вызывает их Git, а ваши два Git имеют сношения с репозиторием, и, поскольку направление было «получать от них что-то новое», что бы они ни имели, у вас теперь есть и оно. Но ваш Git помнит, что они имеют , используя имена 1066 * вашего Git для удаленного слежения . Ваш Git спрашивает их об их master
. Они могут сказать: my master
- это commit a123456...
. Если у вас еще нет этого коммита (и любых более ранних, которые идут с ним), ваш Git заставляет их отправлять этот коммит (и любые более ранние, которые у вас есть, которые вам тоже нужны). Как только ваш Git получает коммит, ваш Git устанавливает ваш origin/master
, а не master
! - чтобы помнить, что их master
говорит a123456
.
Для обновления их клонируют с чем-то новым, что вы получили или сделали - очевидно, вы должны были сделать это сами или получить это где-то не от них - ваш Git вызывает их Gitи скажите: У вас есть коммит b789abc...
? Если нет, вы даете им этот коммит и любые другие, которые вам нужно дать им для выполнения задания. Тогда ваш Git говорит: Теперь, пожалуйста, если хотите, установите master
на b789abc...
. 2 Обратите внимание, что они не задают имя для удаленного слежения! У них нет brian/master
или kevin/master
или чего-либо подобного;у них просто есть их master
.
Если их Git возвращается и говорит ОК, я установил для master
значение b789abc...
, ну, теперь вашGit знает, что их master
означает этот хэш-идентификатор. Таким образом, ваш Git обновляет ваш origin/master
, чтобы помнить, что их мастер запоминает b789abc...
.
Это подводит нас к тому, что имена удаленного отслеживания, такие как origin/master
, являютсявсе о: Это память вашего Git о тех хэш-идентификаторах, которые запоминает их Git. Эти хэш-идентификаторы могут быть устаревшими! При запуске git fetch
ваш Git получает что-то новое от своего Git и обновляет ваши имена для удаленного отслеживания, так что теперь ваш Git имеет нужную информацию. Если с момента запуска git fetch
прошло некоторое время, ваш Git может устареть. 3
1 Это ограничение уникальности на самом деле немного смягчено: если два Git никогда не будут иметь Git-секс друг с другом, один из двух может повторно использовать идентификатор хеша, который другой использует для другого внутреннего объекта. Помимо этого исключения, все Git в решающей степени зависят от уникальности хеш-идентификаторов. Они - то, что заставляет всю магию работать. Именно поэтому почему они выглядят такими случайными, хотя на самом деле они просто криптографические контрольные суммы, которые жестко вычисляются: они должны быть уникальными.
2 Ваш git push
сделает это, даже если у них уже есть b789abc...
. Команда git push
состоит из двух частей: посылает коммиты, если / как необходимо , которая работает с уникальными хэш-идентификаторами, за которыми следуют запросы или команды для другого Git: установить имя ветви X видентификатор хэша H1, установите для имени ветви Y идентификатор хэша H2 и т. д.
3 Сколько времени "время"? Это зависит от того, насколько активен другой Git-репозиторий. Может быть, они получают новые коммиты ежедневно. Может быть, это только раз в год. Или, может быть, они получают тысячи новых коммитов в час, и если это было даже полсекунды, почему, это практически навсегда!
Далее, вы должны понимать, что коммиты соединяются друг с другом
В Git коммит - вещь с уникальным идентификатором хэша - это:
- снимок всех ваших файлов (не меняет на файлы, но полный снимок)
- плюс некоторые метаданные:
- ваше имя и адрес электронной почты, а также отметка даты и времени, когда вы сделали этот коммит: это коммиттер data
- то же самое повторяется дополнительное время, что и данные author (это может отличаться, если вы копируете коммит)
- ваше сообщение журнала, вкоторый вы говорите другим или себе в следующем месяце / году, почему вы сделали этот коммит
- критически важным для Git, необработанный хэш-идентификатор некоторых предыдущих коммитов .
Последний бит - история существования Git. Коммит является снимком, но имеет идентификатор хеша предыдущего снимка. То есть, если у нас есть какой-то большой некрасивый идентификатор хеша - давайте просто назовем его H
для «хеш» - который находит один коммит, тот коммит имеет внутри себя идентификатор хеша предыдущего коммита. Давайте назовем этот второй хэш-идентификатор G
. Затем:
... G <-H
H
идет после G
, но указывает на более раннюю фиксацию G
. Конечно, G
также имеет большой некрасивый хеш-идентификатор, поэтому G
указывает на F
:
... <-F <-G <-H
и F
снова указывает назад, и так далее.
С такой цепочкой мы можем пройти весь путь от любого коммита до самого первого коммита. Этот коммит не указывает на более ранний коммит, потому что он не может: более ранний коммит не существует. Git называет это root commit. Скажем, есть только восемь коммитов, от A
до H
, все в красивой линейной строке, и что name master
содержит идентификатор хеша last commitH
:
A--B--C--D--E--F--G--H <-- master
Мы говорим, что master
указывает на H
. (Я переключил стрелки между коммитами на линии, потому что их легче рисовать, особенно на следующих нескольких рисунках! Но они все еще указывают назад. Имейте в виду, что Git работает в обратном направлении; время от времени полезно знать об этом. Здесь это не имеет большого значения.)
Теперь давайте сделаем еще несколько коммитов, но сделаем их такими, на двух разных ветках br1
и br2
:
I--J <-- br1
/
...--G--H <-- master
\
K <-- br2
Имя br1
содержит идентификатор хеша коммита J
: br1
указывает на J
. Имя br2
указывает на K
.
Одна из хитростей Git в том, что теперь коммиты через H
находятся на во всех трех ветвях. (Другие системы контроля версий этого не делают.) Если мы делаем новый коммит на master
сейчас, он получает еще один новый уникальный хэш-идентификатор, и имя master
перемещается, чтобы указать на него:
I--J <-- br1
/
...--G--H--L <-- master
\
K <-- br2
WhЕсли вы добавите коммиты в репозиторий, ни один из существующих коммитов не изменится вообще. (Они буквально не могут измениться, потому что их уникальный хэш-идентификатор - это просто контрольная сумма их содержимого. Если вы что-то измените в любом коммите, все, что вы получите, это новый и другой, уникальный, коммит с новым,другой, уникальный хэш-идентификатор.) Но имена ветви перемещаются! Имя ветви всегда по определению указывает на последний коммит в ветви.
Теперь мы можем нарисовать вашу ситуацию
Давайте перейдем к вашему Git репозиторий, или один близкий к нему. Я понятия не имею, сколько всего у вас коммитов - вероятно, намного больше, чем 26 прописных букв, которые я могу использовать - но давайте просто нарисуем это так:
...--G <-- origin/Dev-Branch
\
H--I--J--K--L <-- Dev-Branch (HEAD)
* * * * * * * * * * * * * * * * * * * * * * означает, что этофилиал вы проверили прямо сейчас. Если у вас много ветвей - или даже только две - нам нужно знать, на какой вы «включены», чтобы git status
мог сказать on branch Dev-Branch
, и чтобы при создании нового коммита Git знал, какая веткаимя для перемещения.
origin/Dev-Branch
, которое мы нарисовали, это имя для удаленного слежения . Ваш Git поговорил со своим Git - именем на origin
, именем, содержащим URL, который ваш Git использует для общения с ними, - в какой-то момент они сказали, что my Dev-Branch
names commit G
так что ваш Git имеет origin/Dev-Branch
, указывающий на (общий) коммит G
.
Между тем, ваши Dev-Branch
указывают на коммит L
.
Коммиты всегда указывают назад. Новые коммиты указывают на любой коммит, который у вас был, когда вы их сделали, поэтому L
указывает на K
, что указывает на J
и т. Д.
Сколько коммитов есть,если вы начнете считать с L
и остановитесь, когда достигнете коммита, который origin/Dev-Branch
назвал?
Впереди 5, позади 1
Теперь предположим, что должны были выполняться git fetch
и у них был новый коммит - назовем его N
, по какой-то причине пропустив M
- который пришел сразу после коммита H
, в итоге вы получите это в своем хранилище:
...--G-----------N <-- origin/Dev-Branch
\
H--I--J--K--L <-- Dev-Branch (HEAD)
Это потому, что ваш мерзавец спрашивал своих мерзавцев об их Dev-Branch
, и они отвечали: «О, это коммит N
». Ваш Git получит коммит N
, а затем увидит, что вы уже сделали коммит G
и закончите с этой фазой, и тогда ваш Git обновит ваш origin/Dev-Branch
, чтобы он указывал на N
.
Теперьесли у вас есть git status
количество коммитов, сколько коммитов есть на вашем Dev-Branch
, которые не переданы? Сколько коммитов на вашем origin/Dev-Branch
, что aren 't shared? (Обратите внимание, что shared здесь означает между этими двумя именами . Так что коммиты G
-и-ранее являются общими, но H-а позже - нет. Мы не беспокоимся о том, что на самом деле в другом Git, просто о том, что наш Git помнит об их Git.)
Предположим, что это была реальная ситуация в иххранилище (они совершают N
). Даже если у вас не было / не было N
в вашем собственном хранилище, теперь вы можете запустить git push
. Ваш Git вызывал их Git и отправлял им вашу H-I-J-K-L
цепочку, и теперь они будут иметь тот же рисунок, что и у нас (но используя их имена, а не ваши). Тогда ваш Git попросит их изменить их Dev-Branch
, чтобы они указывали на коммит L
:
...--G-----------N <-- Dev-Branch [in origin]
\
H--I--J--K--L <-- proposed new Dev-Branch
Если бы они переместили свое имя Dev-Branch
, чтобы указать на L
что происходит, чтобы совершить N
? Ответ таков: на самом деле с ним ничего не происходит, но теперь у них нет названия для него, и они больше не могут его найти . Стрелки идут только назад: нет пути от G
до N
, только от N
до G
. Поэтому, если вы сделаете это, они просто скажут нет, я не буду двигать Dev-Branch
. (Они назовут это не-перемотка вперед .)
На этом этапе вам нужно будет сделать коммит слияния в вашем собственном репозитории или иным образомубедитесь, что они не потеряют свой коммит N
. Вот как может выглядеть такое слияние:
...--G--------------N <-- origin/Dev-Branch
\ \
H--I--J--K--L--M <-- Dev-Branch (HEAD)
Ваш новый коммит слияния M
будет ссылаться на ваш существующий коммит L
, но также и на их (теперь также ваш) коммит N
. (Если у вас еще не было N
, вам нужно сначала git fetch
, чтобы получить N
.)
После успешного выполнения git push
обновитеВаши рисунки
Вернемся к этому рисунку:
...--G <-- origin/Dev-Branch
\
H--I--J--K--L <-- Dev-Branch (HEAD)
Вы запускаете git push
(или git push origin Dev-Branch
). Ваш Git вызывает их Git, дает им коммиты H-I-J-K-L
, если у них их нет - если у них их есть, ваш Git посылает те, которые им еще нужны, а затем просит их установить Dev-Branch
, чтобы указатьсовершить L
. Они говорят ОК, я сделал , поэтому ваш Git обновляет ваш origin/Dev-Branch
, чтобы помнить, что они приняли ваш запрос:
...--G
\
H--I--J--K--L <-- Dev-Branch (HEAD), origin/Dev-Branch
Теперь, когда git status
считает коммиты, он выяснит, сколько коммитов на вашем Dev-Branch
не передано вашему origin/Dev-Branch
(память вашего Git их Dev-Branch
), и сколько коммитов на вашемorigin/Dev-Branch
, которые не передаются вашим собственным Dev-Branch
. Поскольку эти два имени точно совпадают, вы не делаете никаких коммитов вперед и не делаете никаких коммитов.
Все это основано на информации, которую вы имеете локально. Не имеет значения, какой у них Gitимеет в этот момент. То, что их Git имеет значение, когда вы запускаете git fetch
и когда вы запускаете git push
, но не когда вы запускаете git status
.
Когда вы смотрите на различные коммиты, такие как L
,Git показывает вам, что в этом коммите. Ни один из метаданных не может быть изменен. Ни один из снимков не может быть изменен. В метаданных говорится, что фиксация была сделана вчера, вот что вы увидите.
(Чтобы показать снимок, Git фактически извлечет снимок из L
и снимка изего непосредственный родитель, K
. Затем Git сравнит два снимка, чтобы увидеть, что изменилось. То, что изменилось , в целом более полезно, чем , чем все содержимое , когда выхочу посмотреть на коммит вот так. Но каждый коммит все еще полный снимок.)