Что на самом деле означает git «позади или впереди X коммитов»? - PullRequest
2 голосов
/ 02 ноября 2019

Итак git status вернул

Your branch is ahead of 'origin/Dev-Branch' by 5 commits.
  (use "git push" to publish your local commits)

Я сделал это, и теперь git status возвращает

Your branch is up to date with 'origin/Dev-Branch'.

, но git log показывает, что мой последний коммит был накануне:то есть сегодня ничего не выдвинуто в исходное состояние

Как Git вычисляет сообщения об отстающих и опережающих коммитах?

Ответы [ 5 ]

1 голос
/ 02 ноября 2019

Это очень помогает понять 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 сравнит два снимка, чтобы увидеть, что изменилось. То, что изменилось , в целом более полезно, чем , чем все содержимое , когда выхочу посмотреть на коммит вот так. Но каждый коммит все еще полный снимок.)

0 голосов
/ 02 ноября 2019

Работа с git - это танец между следующей троицей:

  1. Dev-Branch локальный, в настоящее время извлеченный
  2. Dev-Branch в удаленном хранилище
  3. (remotes/)origin/Dev-Branch - локальная копия пульта дистанционного управления Dev-Branch

Впереди / сзади примерно 1 и 3.

3. - это то, что обновляется, когда вы делаетеfetchgit pull = fetch & merge).

0 голосов
/ 02 ноября 2019

сегодня ничто не было отправлено на источник

Ваши коммиты были отправлены на источник сегодня, когда вы запустили команду git push.

Но эти коммиты были сделаны вчера. Временные метки в коммитах показывают время, когда они были зафиксированы, а НЕ когда они были отправлены.

Время, когда ветка была отправлена, может быть записано на GitHub, или сгенерировало некоторые электронные письма, или сохранено git reflog , но на самом деле не записан в самом базовом git. Ваши ветви не хранят отметки времени, когда они были созданы или отправлены.

Как Git вычисляет сообщения об отстающих и опережающих фиксациях?

Если ваши удаленные и локальные филиалы указывают на один и тот же коммит, они "в курсе". В противном случае, один опережает другой, если вы сделали X коммитов.

Когда вы работаете на своем компьютере и делаете коммиты локально, ваша локальная ветвь «опережает» удаленную ветвь. После того, как вы push внесете эти локальные изменения в удаленное хранилище, ваши филиалы будут "в курсе".

0 голосов
/ 02 ноября 2019

Помните, что у вас есть локальная копия всего репозитория git. Когда вы что-то фиксируете, на сервер ничего не отправляется - фиксация сохраняется в вашей локальной копии хранилища. Вы можете сделать столько коммитов, сколько захотите, и никто не увидит их, пока вы не наберете git push.

Отвечая на ваши вопросы:

Ваша ветка впереди 'источника/ Dev-Branch 'на 5 коммитов.

Это означает, что в вашей локальной копии хранилища есть 5 коммитов, которые не находятся в "origin / Dev-Branch". Другими словами, 5 коммитов, которые вы еще не выдвинули.

После того, как вы нажали, все то же самое.

git log показывает только ваши коммиты, а не то, что было или не было выдвинуто,Так что, если ваш последний коммит был вчера, тогда это точно. Дата изменения не изменяется, когда вы ее нажимаете.

0 голосов
/ 02 ноября 2019

, но git log показывает, что мой последний коммит был накануне: т.е. сегодня ничего не было отправлено на источник

Это не то, что означает эта временная метка. Вы совершили сделанный накануне день и отправились на источник сегодня.

Git отслеживает, где находится ваша текущая ветвь в истории, и последнюю полученную информацию о том, где находится восходящая ветка - соответствующая ветка на вашем удаленном (GitHub) -является. Он может рассказать вам, как эти две истории сравниваются. Иногда все, что нужно для синхронизации двух репозиториев (например, вашего и GitHub), - это сообщить одному из них о новых коммитах. Этот процесс не создает никаких новых коммитов, поэтому вы не увидите никаких временных меток внутри Git, связанных с ним.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...