Git тянуть против выборки - нет разницы для вновь полученных веток? - PullRequest
0 голосов
/ 12 апреля 2020

Я читал о git pull и fetch командах и разнице между ними.

Я согласен, что есть разница между двумя командами, когда у нас есть главные ветви как локально, так и удаленно, и поэтому pull объединит любые выбранные нами изменения.

Но что, если новые ветви были перенесены в пульт, который никогда не доставался раньше. Если мы используем только git fetch против git pull, что будет внутренней разницей от Git точки зрения после того, как мы получили / извлекли эти ветви? новые ветви не интегрированы, если мы только запустим git fetch?

Я хотел проверить это и сделал следующее:

У меня есть удаленный репозиторий, который я дважды клонировал, давайте назовем эти локальные репозитории repo 1 и repo 2 - repo 1 создадут новые ветви и отправят их sh на удаленный компьютер, а repo 2 будут извлекать / извлекать их с удаленного узла.

Я создал и перенес новую ветку - side_branch_1 - в удаленное хранилище с repo 1. Затем я вернулся к repo 2 и использовал git pull. Затем я запустил git branch -a и увидел новую ветку как remotes/origin/side_branch_1. Я также открыл файл .git/FETCH_HEAD и увидел строку для этой ветви: <sha-1> not-for-merge branch side_branch_1 of <url>.

После этого в repo 1 я создал и нажал side_branch_2, а в repo 2 я использовал git fetch в этот раз. Затем я снова побежал git branch -a и увидел новую ветвь как remotes/origin/side_branch_2. Я также снова открыл файл .git/FETCH_HEAD и увидел строку для этой ветви: <sha-1> not-for-merge branch side_branch_2 of <url>.

Нет ли разницы для новых ветвей, будь то I pull или fetch? И если да, то в чем отличие от Git внутренней точки зрения ?

Поскольку side_branch_1 помечен как not-for-merge, хотя он был потянул . Почему ? Чего мне не хватает?

1 Ответ

2 голосов
/ 12 апреля 2020

TL; DR

git pull означает выполнить git fetch, затем выполнить вторую Git команду . Первый шаг - git fetch - не влияет ни на одну из ваших веток. Это ничего не меняет, с чем вы работаете, если вы работаете над чем-либо.

Шаг second , который по умолчанию запускает git merge, влияет на текущую ветку . Он не создает новую ветвь, поэтому, как правило, любые новые имена ветвей, созданные в других Git, не имеют значения, если вы явно не назвали их в своей команде git pull.

Предполагая, что вы запускаете git pull без дополнительных аргументов, remote , на котором git pull выполняет git fetch, является удаленным, связанным с текущей ветвью, и фиксация, которая используется для rebase-or-merge, это связано с восходящим потоком текущей ветви, как обновлено на шаге git fetch. Git налагает ограничения на настройки восходящего потока для имени ветви в вашем хранилище: в частности, если ваш Git еще не знает, что какое-то имя существует в другом Git, ваш Git не позволит вам установить это как вверх по течению. Таким образом, «новые» ветви, которые мы не определили должным образом, на самом деле, не имеют отношения.

Если вы добавите больше аргументов в командную строку git pull, картина станет более сложной.

Long

Нет ли разницы для новых веток, тяну я или получаю?

Git pull всегда означает: run git fetch, затем run вторая Git команда . Очевидно, что они разные, потому что git fetch не запускает вторую команду Git. Здесь не имеет значения, видит ли шаг выборки имена ветвей, которых ваш Git не видел раньше.

И если да, то в чем отличие от Git внутренней точки зрения?

Здесь вы должны внимательно следить за тем, как на самом деле работает Git. Чтобы этот ответ был коротким (я sh), я скажу, чтобы увидеть много других моих ответов, но много:

  • Каждый коммит имеет уникальный идентификатор ha sh, это длинное случайное имя коммита, которое git log показывает вам, например: commit 1c56d6f57adebf2a0ac910ca62a940dc7820bb68.
  • Каждый коммит хранит снимок всех ваших файлов. Файлы внутри каждого коммита имеют специальный, только для чтения, Git только сжатый формат, замороженный на все времена.

  • Каждый коммит также хранит некоторые метаданные: информация о коммите, которая не является файлом, сохраненным с коммитом, а содержит информацию о том, кто сделал коммит, когда и почему (их сообщение в журнале). В этих метаданных каждый коммит хранит идентификатор ha sh своего непосредственного родительского коммита (для большинства коммитов; некоторые хранят двух или более родителей, это коммитов слияния , и в по крайней мере один будет самым первым коммитом в репозитории и поэтому не будет иметь родителя).

  • A имя ветви подобно master просто содержит необработанный ha sh ID последнего коммита в цепочке. Следовательно, если у вас есть ветвь с именем master и некоторыми коммитами, master содержит некоторое га sh ID H, а коммит H указывает на какой-то более ранний коммит G, который указывает на еще более ранний коммит F и т. д .:

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

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

    ...--F--G--H--I   <-- master
    
  • Имена ветвей - не единственный вид имен, которые могут запомнить коммиты ha sh ID. Более одного имени может идентифицировать любой отдельный коммит.

Команда git clone работает, вызывая другой репозиторий Git. Вы говорите своей системе:

  1. Создайте новый пустой каталог / папку (или используйте пустую папку, на которую вы указываете git clone).
  2. Создайте новый, пустой хранилище там: git init.
  3. Сохраните URL-адрес для последующего использования под именем origin (или любым другим именем, которое вы скажете Git для использования): git remote add.
  4. Выполните любую другую конфигурацию, которую вы сказали Git, с помощью команды git clone.
  5. Вызовите другой Git в origin - по сохраненному URL-адресу - и попросите перечислить его ветвь (и другие) имена и их необработанные идентификаторы ha sh. Затем попросите Git о коммитах ... в этом случае всех из них. Скопируйте все его коммитов в наш пустой репозиторий. Возьмите имена его ветвей и переименуйте их: например, сделайте его master нашим origin/master и сделайте его develop нашим origin/develop и т. Д.
  6. Наконец, для одного из этих имен - вероятно, master - используйте переименованную origin/ версию имени, чтобы сделать branch name, и укажите это имя ветви на том же коммите, что и моя origin/ версия имени.

Таким образом, после начального git clone у вас есть имена для удаленного отслеживания , обычно в форме origin/*, для каждого другого Git филиал имена. Затем у вас есть одно собственное имя ветки, обычно master, указывающее на такой же коммит, что и на origin/master. Если у них есть master и develop, возможно, теперь у вас есть:

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

Шаг 5 в шестиступенчатой ​​последовательности git clone, приведенной выше, на самом деле git fetch. Однако вместо того, чтобы получать каждый коммит , git fetch делает разговор с другим Git, чтобы увидеть, какие коммиты у них есть, а у вас нет. Во время первоначального клона у вас нет каких-либо коммитов, так что это просто автоматически все их коммиты. Позже это их новые единицы.

Когда вы запускаете git fetch позже, если у них все еще есть master идентифицирующий коммит H и их develop идентифицирующий коммит J ваш Git будет смотреть в вашем хранилище, используя реальные идентификаторы ha sh, за которыми стоят H и J, и вы увидите, что они у вас уже есть. Ваш Git не должен получать новые коммиты. Если они добавили еще один коммит к их develop, у них будет новый коммит K, и вы получите его:

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

и затем ваш git fetch обновит ваше имя удаленного отслеживания origin/develop, чтобы оно указывало на фиксацию K:

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

Если они сделают что-то необычное и заставят их develop вернуться один шаг, и вы снова запускаете git fetch, вы будете сохранять фиксировать K некоторое время - обычно не менее 30 дней по умолчанию - но ваш Git настроит ваш origin/develop в соответствии их develop:

...--G--H   <-- master, origin/master
      \
       I--J   <-- origin/develop
           \
            K   [no name: hard to find!]

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

(Для каждого имени есть скрытые журналы ранее сохраненных идентификаторов ha sh, по которым вы можете найти K. Записи в этих журналах в конце концов истекают, и вот где 30-дневный лимит исходит из: по истечении 30 дней срок действия записи, сохраняющей K, истекает. at, Git s сборщик мусора , git gc, выбросит K по-настоящему, если никто не создал новое имя для его защиты.)

Запуск git fetch как этот, без имени вообще - по умолчанию origin, обычно - или только с именем пульта, таким как origin, будет - до тех пор, пока вы не настроите настройки специально - получите все имен ветвей от других Git и соответственно создайте или обновите все ваших имен для удаленного слежения. Однако настройка так называемого клона с одной ветвью настраивает ваш Git по-другому, так что git fetch обновляет только одно имя удаленного отслеживания. Вы можете перенастроить это позже или переопределить набор имен для обновления, используя refspe c, но мы не будем go более подробно здесь.

Пока это все о git fetch; давайте начнем использовать имя ветви

Опять же, Git 'fetch - это часть, которая получает новые коммиты от другого Git. Получив новые коммиты, если таковые были получены, git fetch корректирует ваши имена для удаленного слежения . Это не влияет ни на одно из ваших названий веток . Все ваши имена ветвей не потревожены.

Если у вас никогда нет собственных имен ветвей, что было бы странно, хотя это возможно, и никогда не выполняйте работу на вашем own, что менее странно и разумно для определенных приложений (например, для хранения в архиве). Но вы, вероятно, действительно используете ветки.

Допустим, вы делаете своим собственным названием ветви, dave или любым другим. Допустим, вы указали это имя для существующего коммита H:

...--G--H   <-- dave, master, origin/master
      \
       I--J--K   <-- origin/develop

Теперь, когда у вас есть более одного имени ветки, мы бы хотели, чтобы Git запомнил, какое из них вы на самом деле используете , Мы добавим специальное имя HEAD к одному из них:

...--G--H   <-- dave (HEAD), master, origin/master
      \
       I--J--K   <-- origin/develop

Так что теперь мы можем сказать, что вы используете name dave и commit H. Три имени, dave и master и origin/master, все прямо сейчас определяют коммит H.

Мы упоминали выше, что файлы, сохраненные в коммитах, находятся в специальном, только для чтения, Git -только сжатый и замороженный формат, который может использовать только Git. Итак, Git скопировал эти файлы в *1266* index и рабочую область для Git. Рабочая область - ваше рабочее дерево или рабочее дерево . В нем хранятся обычные файлы в обычном формате вашего компьютера.

Вы делаете новые коммиты - обычно в любом случае - манипулируя этими обычными файлами, а затем используя git add, чтобы скопировать их обратно в индекс Git. Это повторно сжимает файл в замороженный формат, готовый к go в новый коммит. Когда вы запустите git commit, Git упакует файлы, которые находятся в его индексе на тот момент. Следовательно, мы можем сказать, что основная функция индекса заключается в сохранении того, что вы предлагаете поместить в ваш следующий коммит . (У него также есть и другие функции, но мы не будем вдаваться в них здесь.)

В конечном итоге у вас есть файлы в форме и git add -ed, и вы запускаете git commit. Git собирает соответствующие метаданные и записывает новый коммит, который назначает новому коммиту его уникальный идентификатор sh. Git затем сохраняет идентификатор новой фиксации ha sh в текущую ветку name , давая нам:

          L   <-- dave (HEAD)
         /
...--G--H   <-- master, origin/master
      \
       I--J--K   <-- origin/develop

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

Теперь, если вы запустите git fetch и они , кем бы они ни были, сделаны или иным образом приобрели новые коммиты, которые вы еще не видели, эти новые коммиты будут добавлены в их ветви. Ваш Git видит их в своем хранилище, видит, что у вас их еще нет, и получает их. Давайте нарисуем один (и перестанем рисовать I-J-K, поскольку они в пути, но буквы израсходованы, поэтому я go с M здесь далее):

          L   <-- dave (HEAD)
         /
...--G--H   <-- master
         \
          M   <-- origin/master

Вы можете как включить их новый коммит каким-либо образом.

Точно как вы включите их новый коммит - решать вам. Например, вы можете:

  • git checkout master, а затем git merge origin/master
  • git merge origin/master прямо сейчас, когда совершаете коммит L на ветке dave

или делайте любое другое.

Если вы:

git checkout master; git merge origin/master

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

          L   <-- dave
         /
...--G--H--M   <-- master (HEAD), origin/master

На самом деле, если вы запустите git checkout master; git rebase origin/master, то же самое произойдет в этом частный случай. В других случаях могут происходить разные вещи.

Здесь git pull входит

Как правило, после того, как вы принесли новые коммиты из других Git с git fetch, вы, как правило, хотите что-то с ними сделать . Если вы используете master, и они обновили свои master, вам нужно обновить master. Два наиболее распространенных способа сделать это - запустить git merge или git rebase.

. Команде git pull может быть предписано запустить любой из них в качестве второй команды. По умолчанию он запускается git merge. И git merge, и git rebase работают с текущей веткой. То есть они смотрят на специальное имя HEAD. Пока это связано с каким-то именем ветви - как обычно, - это ваше имя ветви, на которое они будут влиять. Они вносят изменения в индекс Git и в ваше рабочее дерево; оба могут изменить то, какой коммит выбран текущим именем ветки; git merge может сделать новый коммит слияния, или выполнить операцию ускоренной перемотки вперед, или иногда ничего не делать.

Одна из частей, которые мне не нравятся в git pull, - это то, что вы не всегда знаете, , когда вы нажмете Enter , то, что фиксирует git fetch, в итоге будет извлечено, и куда он может переместить любые имена для удаленного слежения. Но у вас мертвая установка на с git merge или git rebase с использованием этих новых коммитов и обновленных имен. (Технически это немного не так, как мы увидим - он не использует обновленные origin/* имена напрямую, но здесь достаточно близко.)

Даже если новые коммиты не являются что-то, что вы хотите использовать, чтобы повлиять на вашу текущую ветвь, это произойдет. Вы не можете сказать , если это произойдет. Вы можете использовать какой-то просмотрщик, чтобы сначала проверить другой репозиторий Git, но что произойдет, если вы просмотрите его, а затем непосредственно перед тем, как нажать Введите , кто-то еще изменит вещи в этом другом хранилище? Тем не менее, людям это очень нравится и они используются постоянно, поэтому давайте перейдем к вашим подробным вопросам.

Я также снова открыл файл .git/FETCH_HEAD и увидел строку для этой ветви: <sha-1> not-for-merge branch side_branch_2 of <url>.

Вот исторический секрет (или не очень секретный) о git fetch и git pull: они настолько стары, что существовали git pull до того, как имена для удаленного слежения, такие как origin/master, существовали , Remotes и имена для удаленного слежения были изобретены некоторое время между Git версиями 1.4 и 1.5, и некоторые возились с разными идеями. Команда git pull продолжала работать так, как этого хотели люди, на протяжении всего переходного периода, когда разрабатывались новомодные пульты и имена для удаленного отслеживания.

Чтобы избежать необходимости слишком часто менять код, и / или из-за того, что имена удаленных пользователей и слежения за ними еще не существуют, git fetch всегда записывал все в .git/FETCH_HEAD Чтобы ранние сценарии git pull выяснили, какой зафиксирует га sh ID , чтобы дать git merge, git fetch отмечает, какое из наших названий веток мы используем теперь - это проверка «где находится HEAD» - и какие имена использовать из other Git. Затем он помечает каждую .git/FETCH_HEAD строку not-for-merge или не помечает ее, в зависимости от аргументов, которые вы указали git fetch.

Когда вы запускаете git pull, вы можете задать кучу Аргументы команды git pull:

git pull                 # no arguments at all
git pull origin          # just a remote
git pull origin master   # a remote and a branch name *on the remote*

Назад, когда git pull буквально запустил git fetch, он передал эти аргументы git fetch. Теперь в него встроено git fetch, но оно все равно работает. Если вы дадите здесь одно или несколько названий ветвей, то есть или те, которые git fetch не помечают как not-for-merge в файле .git/FETCH_HEAD.

Точно так же, когда git pull был еще сценарием оболочки - он был переписан в C относительно недавно - вот как git pull решил, какой идентификатор ha sh передать git merge или, если вы выберете git rebase как ваша вторая команда, git rebase. То, что он делает сейчас, более неясно. Поскольку часть выборки теперь встроена как вызовы функций C, она может просто сохранять необработанные идентификаторы ha sh в памяти.

В Git версии 1.8.4, Git люди решили, что git fetch origin master должен обновить origin/master. До этого git fetch origin обновлял все имена для удаленного слежения, но git fetch origin master обновлял нет . Начиная с Git 1.8.4 и далее git fetch origin master обновляет origin/master. Он по-прежнему не обновляет другие имена для удаленного слежения origin/*, поскольку не переносит коммиты, соответствующие каким-либо обновленным именам. (В некоторых случаях он может обновлять имена удаленного отслеживания, но это не так.)

Заключение

git fetch, который запускает git pull:

  • в основном получает аргументы, которые вы приводите: например, git pull xyzzy one two three запускает git fetch xyzzy one two three. «В основном» здесь только потому, что некоторые опции влияют на то, какую вторую команду использовать, и / или съедаются самим git pull и / или передаются второй команде, а не git fetch .
  • извлекает данные из названного удаленного (или из заданного URL, но это многое меняет) и тем самым обновляет некоторый набор имен удаленного отслеживания ;
  • записей все, что он сделал в .git/FETCH_HEAD на тот случай, если вы все еще используете старые сценарии оболочки git pull.

В общем, git fetch безопасно запускать в любое время. (Вы можете настроить его как небезопасный, если вы действительно используете sh, установив remote.<em>name</em>.fetch ненадлежащим образом или передав небезопасный аргумент refspe c. Однако стоит отметить, что git fetch имеет встроенные проверки безопасности , даже если вы делаете это . Старый сценарий pull отключает их! )

Последующие git merge или git rebase работают в текущей ветке , и это не очень хорошая идея, если это случится, если вы не выполняете работу. Git обычно обнаруживает такой случай и вообще не позволяет запустить вторую команду для этих случаев. В далеком прошлом, однако, команда pull могла (и действительно) срывала текущую работу безвозвратно, потому что git pull - старый сценарий, во всяком случае - отключил много проверок безопасности.

В любом В этом случае вторая команда - шаг слияния или ребазирования - получает кучу дополнительных аргументов, которые заставляют ее работать одинаково в течение переходного периода от Git 1,4 до 1,6, когда менялись имена удаленных и отслеживаемых удаленных объектов. Это было почти 15 лет go сейчас, но все равно работает так же. Если вы используете:

git fetch
git merge

и ваш Git делает коммит слияния, сообщение о слиянии по умолчанию будет выглядеть примерно так:

merge branch origin/dave into dave

, но если вы используете:

git pull

сообщение о слиянии по умолчанию будет больше похоже на:

merge branch dave of <url> into dave

«что-то вроде» происходит потому, что точное написание каждого сообщения здесь зависит от названий ветвей (очевидно), и от того, сливаются в master - здесь пропущена часть into <branch> - и вставляются некоторые кавычки, которые я не хотел бы здесь беспокоить. : -)

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