Git впереди локальный мастер, несмотря на отсутствие изменений - PullRequest
0 голосов
/ 07 мая 2020

Приносим извинения, если это очевидно, я не git эксперт.

Сегодня утром я зашел в свой локальный репо и сделал следующее:

$ git fetch
$ git checkout master

И получил сообщение, что Я видел много раз раньше:

Switched to branch 'master'
Your branch is ahead of 'origin/master' by 8 commits.
  (use "git push" to publish your local commits)

Проблема в том, что я никогда не вносил никаких изменений в эту ветку. Я пробовал git pull, но он просто отвечает 'уже обновлено'.

'Git log origin / master..master' показывает несколько коммитов в ветку от других авторов, которые не отображаются в удаленная ветка, поэтому сообщение кажется логичным, но я не понимаю, как я могу иметь коммиты от других авторов в моей локальной копии, которых нет в удаленном репозитории

Как мне добраться до конца этого?

1 Ответ

1 голос
/ 07 мая 2020

TL; DR заключается в том, что предположение Лассе В. Карлсена в комментарии :

Возможно, кто-то принудительно отправил на пульт и удалил эти коммиты после того, как вы получили их в ваш локальный репозиторий?

, вероятно, ответ. Запустите:

git reflog origin/master

и найдите строку forced-update в выводе; если вы видите это, то вот что произошло.

Длинный: что все это означает

Git, по сути, действительно все о коммитит . Коммиты - это единица хранения, с которой мы работаем. У каждого коммита есть своего рода «истинное имя», одинаковое для всех в каждом Git, которое является его необработанным ha sh ID - большой уродливой строкой букв и цифр, например b34789c0b0d3b137f0bb516b417bd8d75e0cb306. Этот ha sh ID означает этот коммит, и если у вас есть клон репозитория Git для Git, у вас либо есть этот коммит (часть предстоящего выпуска Git 2.27), либо вы этого не сделаете - ну, пока.

Однако мы обычно не ссылаемся на коммиты по этим идентификаторам ha sh. Они слишком большие и некрасивые, их невозможно запомнить или перепечатать. Нам нравится использовать имена: имена веток, например master, имена тегов, например v2.26.0, 1 или имена удаленного отслеживания например origin/master.

Чтобы теги работали правильно во всех случаях, мы должны пообещать никогда не изменять идентификатор ha sh, на который ссылается какой-либо тег. Некоторое современное программное обеспечение, в частности Go модули и Go кэш версий - полагается на этот для бесперебойной работы (хотя вы можете ограничить его в целях безопасности, если / при необходимости).

Особенность имен веток - по сравнению с именами тегов - заключается в том, что sh ID коммита, который мы получаем, обычно просматривая имя ветки , меняет . Имя ветки фактически определено в Git как хранящее идентификатор ha sh последней фиксации, которая должна считаться частью ветки.

Коммиты неизменяемы , потому что их настоящее имя ha sh ID - это криптографическая c контрольная сумма их содержимого. 2 Между тем каждая фиксация содержит ha sh ID каждого своего непосредственного предшественника или родителя коммиты - обычно всего один, а слияния обычно два. Эта форма фиксируется в узлах в пределах ориентированного ациклического c графа, заставляя каждую фиксацию действовать как вершина, названная ha sh ID, и соединяющие односторонние ребра или дуги, названные как родительские.

Этот график может быть - и добавлен - просто добавлением новых коммитов в набор всех коммитов в репозитории. То есть мы используем git checkout или git switch, чтобы выбрать имя ветки в качестве текущей ветки и выбрать его последнюю фиксацию - ha sh ID, сохраненный в имя ветки - как текущая фиксация . Затем мы делаем свою работу как обычно, и Git упаковывает новый снимок и новый набор метаданных, чтобы сделать новую фиксацию. Родителем новой фиксации является текущая фиксация. 3 Теперь, когда новая фиксация существует и безопасно хранится в репозитории, 4 Git записывает новую уникальную ha * 1365 новой фиксации. * ID в текущей имя ветки , чтобы ветвь автоматически указывала на (новую) последнюю фиксацию.

Вот как ветви растут, по одной фиксации за раз, и это обеспечивает «нормальное направление» для перемещения имен веток:

... <-F <-G <-H   <-- master

становится:

... <-F <-G <-H <-I   <-- master

, когда мы добавляем новую фиксацию I, затем становится:

... <-F <-G <-H <-I <-J  <-- master

когда мы добавляем фиксацию J и т. д.

Фактически мы не можем удалить коммитов из этого графика, но мы можем принудительно переместить любое имя ветки назад как бы. Например, после добавления коммитов I и J мы можем «удалить» их для большинства практических целей, заставив имя master содержать ha sh ID фиксации H снова:

             I--J   ???
            /
...--F--G--H   <-- master

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

Ваши собственные Git имена удаленного отслеживания , например origin/master, являются вашей Git памятью некоторых других Git имен веток. То есть, когда вы впервые клонируете репозиторий или используете git fetch для обновления клона, ваш Git вызывает какой-нибудь другой Git через URL-адрес, который Git сохраняет под именем. Мы называем имя - в данном случае origin —a remote и указываем имя как сокращение для URL:

git fetch origin

Наш Git вызывает их Git , получает от них список имен их веток и last-commit-ha sh -ID, а также получает от них любые коммиты, которые у них есть, которых у нас нет и которые нам нужны. Например, если у нас есть, в это время:

...--F--G--H   <-- master, origin/master

мы можем вызвать их Git и, возможно, их master теперь указывает на новую фиксацию J:

...--F--G--H--I--J   <-- master [in the Git at origin]

Наш Git проверяет, замечает, что у нас нет фиксации с ha sh ID J, и получает эту фиксацию из их Git. Это также приводит к фиксации I, потому что мы всегда получаем все, что у нас нет, 5 , а затем обновляем нашу origin/master - нашу память их master. Итак, теперь у нас есть в нашем собственном локальном репозитории:

...--F--G--H   <-- master
            \
             I--J   <-- origin/master

На данный момент их хозяином является 2 ahead - на два коммита впереди - наш master. Теперь мы можем предпринять действия, чтобы добавить эти коммиты, I-J, в наш собственный master. Мы можем сделать это Git, сдвинув name master «вперед», чтобы указать на фиксацию J:

...--F--G--H
            \
             I--J   <-- master, origin/master

Если мы сделаем это с помощью git merge, Git называет это слиянием с перемоткой вперед . Фактического слияния не происходит: Git на самом деле просто проверяет фиксацию J напрямую и обновляет имя master, чтобы указать на фиксацию J. (Есть и другие способы перемотки вперед любого имени ветки: если фиксация не извлечена, а это не наша текущая ветка, имя просто «скользит вперед» ".)

Но, как мы отметили выше, мы можем заставить имя ветки« двигаться назад », чтобы« удалить »- ну, вроде как-удалить - фиксацию. Предположим, кто-то делает это на другом Git после того, как мы добавили I-J к нашему master. Их репозиторий теперь имеет master, указывающий на фиксацию H. 6 Когда наш Git вызывает их Git - запустив git fetch origin - мы видим, что их master имена фиксируют H, а не фиксируют J. У них нет новых коммитов, поэтому наш Git просто обновляет наш origin/master сейчас, давая нам следующее:

...--F--G--H   <-- origin/master
            \
             I--J   <-- master

Мы теперь 2 ahead из них, поскольку if мы сделали коммиты I-J.

Если нам разрешено git push origin master, мы можем легко вернуть эти два коммита, просто запустив git push origin master сейчас. Если их Git действительно отклонил коммиты I-J, что ж, теперь они вернулись. Если нет, у них уже есть I-J, а наш git push просто заставляет их перемотать вперед их имя master, чтобы указать на фиксацию J.

Кто бы ни "удалил" коммит Для I-J из репозитория Git по адресу origin была причина, по которой они это сделали. Это было намеренно? Это была ошибка? У нас нет способа узнать - ну, у нас есть один способ: спросите их! Они знают; мы можем только догадываться, поэтому спросите их.

Однако мы можем сказать, что это произошло, если наш Git забрал I-J от них в какой-то момент и обновил наши собственный origin/master соответственно. Здесь и появляются те скрытые имена, которые я упомянул.


1 В репозитории Git для Git, v2.26.0 означает фиксацию 274b9cc25322d9ee79aa8e6d4e86f0ffe5ced925, хотя имя фактически преобразуется в объект тега ha sh ID, adf6396efeb4e8c12fb07174b4074c4031b2c460. Однако весь смысл тега в том, что нам не нужно , чтобы знать что-либо из этого. Достаточно простого, удобочитаемого и понятного имени v2.26.0.

2 На самом деле это верно для всех Git объектов - оно не указывает c на фиксацию объектов. См. Также Как вновь обнаруженная коллизия SHA-1 влияет на Git?

3 Если новая фиксация - слияние фиксация, его первый родитель - это текущая фиксация. Остальные родители - вот что делает коммит слияния; обычно здесь есть только один другой коммит - это могут быть идентификаторы ha sh любого существующего коммита, но каждый должен быть идентификатором ha sh некоторого существующего коммита.

4 Обратите внимание, что дерево работ не в репозитории - это отдельная сущность, которая находится рядом с репозиторием, в смысл - и ваши файлы не безопасны до фиксации.

5 За исключением того, что Git называет мелкими клонами. Давайте проигнорируем их здесь.

6 Их репозиторий может или не может все еще содержать коммиты I-J, в зависимости от того, как быстро их Git успеет отбросить недоступен совершает. Доступность - ключевое понятие здесь; для (гораздо) более подробной информации см. Think Like (a) Git.


Reflogs сохраняют предыдущие значения имен

Каждый раз, когда наш Git обновляет любое из наших имен - master или origin/master или даже имя тега - наш Git будет (необязательно, но он включен по умолчанию для нас) сохранит старое значение имени в журнале. Этот журнал представляет собой reflog для данного имени. 7 Это означает, что наша origin/master - наша память о том, где был master, обновляется каждый раз, когда мы запускаем git fetch - отслеживает, где их master был на предыдущем git fetch.

Предположим, кто-то намеренно или случайно заставляет origin имя master переместиться назад - на go от фиксации J до H в приведенном выше примере. Предположим далее, что мы запустили git fetch, который ранее записал это имя, указывающее на фиксацию J, а теперь мы запускаем git fetch, а имя указывает на H. Наш Git принудительно обновит наш origin/master без перемотки вперед. 8

Теперь мы можем, постфактум, найти это в рефлоге для origin/master:

$ git reflog origin/master

В моем случае я могу сделать это с моим Git репозиторием для Git, потому что ветвь pu все время перемещается так:

$ git reflog origin/pu
dfeb8fbf42 (origin/pu) refs/remotes/origin/pu@{0}: fetch: forced-update
fc307aa377 refs/remotes/origin/pu@{1}: fetch: forced-update
5525884f08 refs/remotes/origin/pu@{2}: fetch: forced-update
...

Часть «принудительного обновления» здесь означает, что у них были некоторые коммиты в своей ветке, которые мы смогли получить и запомнить с нашим собственным именем для удаленного отслеживания, но затем они решили отбросить эти коммиты в сторону. Наш Git подобрал свое новое имя pu и соответственно обновил наше origin/pu.


7 Существует также рефлог для специального имени HEAD, но у него нет важной функции в данном конкретном случае, потому что мы не можем быть "на" имени удаленного отслеживания, например origin/master.

8 Команда git fetch сообщает об этом в своем выводе, хотя многие не обращают на это внимания. В каждой строке вы увидите три индикатора: ведущий знак плюс, три точки вместо двух и добавленные слова «принудительное обновление»:

   b34789c0b0..07d8ea56f2  master     -> origin/master
   232c24e857..5cccb0e1a8  next       -> origin/next
 + fc307aa377...dfeb8fbf42 pu         -> origin/pu  (forced update)
   139372b246..6b33381979  todo       -> origin/todo

Обратите внимание, как ветвь pu переместилась в без перемотки вперед, и git fetch сказал нам это три раза.

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