Полностью обновить локальное репо с удаленного - PullRequest
0 голосов
/ 23 января 2020

У меня есть небольшой проект, над которым я работаю с двух разных компьютеров. Я регулярно git push на github (достаточно удивительно, что пульт называется origin), но иногда я работал неделю на одном компьютере, прежде чем вернуться к другому. И когда я вернусь, я просто хочу полное обновление. Все старые ветви были удалены, все новые ветви удалены, и т. Д. c.

Я мог бы полностью удалить локальный проект, затем git clone репозиторий origin. Это кажется грязным, но это не большой проект, поэтому он занимает несколько секунд и выполняется в основном двумя командами.

Есть ли такой же быстрый и простой способ сделать это внутри самого git?

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

Ответы [ 3 ]

1 голос
/ 23 января 2020

Здесь следует помнить, что «ветви» - точнее, ветви names - на самом деле ничего не значат для Git. Они означают что-то для вас , но для Git каждое имя - это просто метод нахождения одного указанного c коммита. Что действительно волнует Git, так это коммитов в репозитории.

Каждый коммит имеет свой уникальный большой уродливый идентификатор ha sh. Например, вы видите эти идентификаторы в выводе git log. Истинные маги c из Git заключаются в том, что каждый Git репозиторий везде согласится с тем, что, если один конкретный c коммит в вашем репозитории - или один в их - имеет некоторое ха sh ID H , нет других коммитов где-либо может когда-либо иметь , что га sh ID. 1 Так что когда Два Git хранилища "встречаются", по git fetch или git push, им нужно только сравнить га sh ID . В вашем GitHub-репозитории на GitHub есть некоторые коммиты с некоторыми ha sh ID H , перечисленными под их name master. Ваш Git вызывает их Git. Их Git говорит: Мое имя master соответствует га sh ID H. Ваш Git чек: Есть ли у меня H? Если ваш Git имеет H уже сделано. Если нет, ваш Git просит их Git отправить коммит H .

У ваших собственных Git есть ваши собственные имена. Один из них может быть master. У него есть га sh ID. Неважно, какой у вас sh идентификатор вашего master, единственное, что имеет значение для вашего Git для операции fetch, это: У меня вообще есть коммит H? Ваш Git может всегда искать все свои внутренние Git объекты напрямую по необработанному идентификатору ha sh. У вас уже есть H , или у вас его нет. Поскольку ни один Git нигде не может когда-либо использовать га sh ID H для чего-либо, кроме этого коммита, все, что ваш Git должен сделать, это проверить эту вещь.

Если у вас нет , в конце концов, H , у вашего Git будет Git коммит для отправки H . Теперь еще одна вещь о каждом коммите: каждый коммит записывает некоторый набор parent commit ha sh ID. Родителем или родителями коммита является коммит (или для слияния, двух или более коммитов), который приходит «непосредственно перед» этим коммитом. То есть, учитывая длинную строку коммитов, сделанных по одному, каждый из них хранит предыдущий идентификатор комита ha sh в обратной цепочке:

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

Так что, если вы собираетесь получить H , их Git теперь предложит вам G . Ваш Git спрашивает себя: У меня уже есть G? Если нет, ваш Git говорит ОК, дайте мне это тоже. Если у вас есть G , ваш Git говорит Нет, спасибо, он у меня уже есть. Это повторяется для каждого их коммита, который у вас нет. В конце концов, git fetch имеет список всех коммитов, которые он должен отправить, и все ваши коммиты, которые этот список коммитов расширит.

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


1 Технически, два разных коммита могут иметь одинаковое значение ha sh ID, но только если они никогда не "встречаются". То есть, если вы подключаете ваш Git к какому-либо другому хранилищу, и он имеет некоторую фиксацию с ha sh ID H , а ваш Git имеет другой H , оба Gits поверит, что это тот же коммит, и ни один не отправит его другому. Пока ваши Git и их Git никогда не встречаются и не пытаются обмениваться коммитами, это не вызывает проблем. На практике, такого рода столкновения ха sh не станет даже удаленно вероятным, пока у вас не будет более 10 17 объектов. В этот момент у вас появляется такой же шанс, как если бы система хранения вашего компьютера случайно вышла из строя на вас, что также пагубно. Это может быть проблемой, если кто-то тщательно спроектирует столкновение ха sh. Подробнее см. Как недавно обнаруженное столкновение SHA-1 влияет на Git?


Git использует имена для поиска last commits

Мы нарисовали простую цепочку коммитов выше, заканчивающуюся коммитом H:

...--F--G--H

(где буквы обозначают реальные идентификаторы ha sh, которые выглядят совершенно случайными, но фактически полностью определены c). Учитывая идентификатор последнего коммита H, мы просто Git ищем H. Внутри H, Git находит га sh ID G, что позволяет ему искать G. Внутри G, Git находит га sh ID F, что позволяет ему искать F и так далее. Это позволяет Git go от последнего фиксировать весь процесс до самого первого в истории коммита.

Это работает даже при наличии ветвящихся структур фиксации:

          I--J
         /
...--G--H
         \
          K--L

Теперь два последних коммитов. J - последний коммит в одной структуре, а L - последний коммит в другой структуре. Две структуры - должны ли мы называть их ответвления ? - встречаются, когда они возвращаются к H, и затем они разделяют коммиты вплоть до начала времен (предположительно, коммит A).

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

Итак, Git предлагает нам возможность использовать имя для запоминания одного (1) га sh ID. Мы можем выбрать имя branch1, чтобы запомнить га sh ID J, и имя branch2, чтобы запомнить га sh ID L:

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

Мы можем, если мы хотите, используйте имя master, чтобы запомнить ha sh ID H:

          I--J   <-- branch1
         /
...--G--H   <-- master
         \
          K--L   <-- branch2

Неважно, что после H есть коммиты. H является последним коммитом в master. Это полное определение ветви. Вот и все: имя ветки - это просто указатель в Git; это просто способ хранить один идентификатор ha sh, и по определению, какой бы идентификатор ha sh не имел имя, это последний коммит в этой ветви. 2

Таким образом, ветвь branch1 заканчивается при коммите J, и автоматически включает каждый коммит, к которому вы можете добраться, начиная с J и работая в обратном направлении. Ветвь branch2 заканчивается при коммите L и включает все коммиты до L, с Git снова работающим в обратном направлении. Git всегда работает в обратном направлении. Если по какой-то причине ему нужно работать вперед, он делает это, сначала работая в обратном направлении и запоминая ha sh идентификаторов, а затем продвигаясь вперед по запомненному списку. И коммиты могут быть, и очень часто, на более чем одной ветви .

Когда ваш Git получает новые коммиты от их Git, ваш Git должен установить несколько имен, чтобы помнить идентификаторы ha sh, которые их Git имели в их именах ветвей. Но их Git рассказывает вам об этом прямо в начале git fetch. Вы запускаете git fetch origin, а Git в начале - по URL-адресу, указанному под именем origin, - говорит: My master содержит H, мой develop содержит L, ... . Ваш Git только что получил весь этот список.

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

Так что теперь ваш Git берет все их ответвления имен и переименовывает этих имен. Ваши Git превращают их master в ваши origin/master. Ваши Git превращают их develop в ваши origin/develop. Это продолжается для всех их названий филиалов. Это Git имена для удаленного слежения , потому что они запоминают ("отслеживают") имена ветвей и идентификаторы ha sh, которые ваш Git видел в их Git под вашим remote name origin.

Итак, допустим, у вас есть это до запуска нового git fetch:

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

У вас обоих есть только одна ветвь, с именем master, и оба эти имени идентифицируют коммит H. Затем вы запускаете git fetch. Их master теперь указывает на новый коммит J, и у них есть имя ветки develop, которое указывает на коммит L:

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

Вы не должны ничего делать , но если хотите, вы можете Git переместить ваше имя master, чтобы указать J. Есть много способов сделать это, но часто самый простой - это сначала git checkout master, если это необходимо. 3 Это прикрепляет специальное имя HEAD к имени master, так что Git знает какую имя ветви использовать для операций, которые записывают новые идентификаторы ha sh в текущую ветку:

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

Операция git checkout также упорядочивает вашу index (он же область подготовки ) и рабочее дерево (он же рабочее дерево ), чтобы вы могли просматривать и / или работать с указанным коммитом по названию филиала. Таким образом, H теперь является вашим текущим коммитом , а master является вашим текущим ответвлением . Мы не будем go подробно описывать здесь индекс и рабочее дерево, но они очень важны: именно там вы создаете свой следующий коммит и как вы работаете с файлами, которые Git хранит внутри коммитов в специальном, только для чтения, Git -только формате.

В любом случае, теперь, когда вы находитесь в этой конкретной ситуации, вы можете Git сказать Операция «слияния» с ускоренной перемоткой вперед-не-слиянием , чтобы ваши master догнали их origin/master:

git merge --ff-only origin/master

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

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

Теперь у вас есть коммит J, и вы можете видеть (и работать с) его файлы в вашем рабочем дереве. Ваше имя master теперь идентифицирует тот же коммит, что и их имя origin/master, и у вас все еще есть все коммиты, которые у них есть, а у вашего Git, для его имен для удаленного отслеживания, есть имена их ветвей.


2 К добавить коммит в ветку, вы делаете это с помощью Git:

  1. Выберите название этой ветви и его последний коммит, например, git checkout branch1.
  2. Выполните все, что нужно, чтобы сообщить Git о создании нового коммита.
  3. Ваш Git записывает новый коммит, который получает новый уникальный идентификатор ha sh. родительский этого нового коммита - это коммит, выбранный вами на шаге 1. Тогда ваш Git просто записывает идентификатор ha sh, созданный здесь, на шаге 3, в имя Вы выбрали на шаге 1.

Теперь имя ветви идентифицирует последний коммит в ветви, как это было до того, как вы сделали новый коммит. Новый коммит является последним коммитом в ветви!

Наглядно:

...--G--H   <-- branch (HEAD)

становится:

...--G--H   <-- branch (HEAD)
         \
          I

на мгновение, но затем Git немедленно записывает I ха sh ID в имя branch. Git знает, что branch является правильным именем, потому что специальное имя HEAD является , прикрепленным к имени branch. Итак, теперь у нас есть:

...--G--H
         \
          I   <-- branch (HEAD)

, и нет причин не просто рисовать их всех по прямой линии.

3 В Git 2.23 и более поздних , вы можете использовать git switch вместо git checkout. Причина для этого заключается в том, что git checkout, как команда, выполняет слишком много разных заданий. Таким образом, в Git 2.23 он был разделен на две отдельные команды: git switch выполняет половину своей работы, а git restore выполняет другую половину. Если у вас более старый Git или вы привыкли к старому способу действий, старая команда git checkout по-прежнему работает так же, как и всегда.


Сокращение

Примечание что если они удаляют имя ветви, ваш Git все равно сохраняет в памяти их имя. То есть, предположим, что они решают, что коммиты K-L ничего не стоят и просто полностью sh их develop называют. У вас есть это в вашем хранилище:

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

, и вы запускаете git fetch, и ваш Git вызывает Git в origin. Они перечисляют тот факт, что их master идентифицирует коммит J, и это все для их ветвей. Ваш Git говорит ах, у меня есть коммит J уже , и они не отправляют вам коммитов, а два Gits отключаются. Ваш Git обновит ваш origin/master, изменив его с указанием на J на указание на J, что не изменит его, поэтому здесь ничего не происходит. И тогда ваш Git завершен, и ваш origin/develop все еще помнит коммит L, даже если у них больше нет develop.

Если вы этого не хотите - если Вы хотите избавиться от вашего origin/develop - вы просто говорите своему Git до обрезке , когда оно доставается. Поскольку ваш Git получает полный список всех своих ветвей, ваш Git может видеть, что у него больше нет develop. Таким образом, ваш Git теперь будет удалять ваш origin/develop, оставляя вам:

...--G--H--I--J   <-- master (HEAD), origin/master
         \
          K--L   [abandoned]

Чтобы выполнить это сокращение, запустите git fetch --prune. Чтобы все операции git fetch автоматически сокращались, когда это возможно, настройте fetch.prune на true:

git config --global fetch.prune true

, например.

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


4 Когда вы отказываетесь от своих собственных коммитов, ваши Git имеют тенденцию запоминать свои идентификаторы ha sh как минимум еще 30 дней в одной или нескольких записях reflog . Это поддерживает оставленные коммиты живыми не менее 30 дней, на случай, если вы захотите их вернуть. В этом случае, однако, больше нет записей reflog, так что это «возможное», как только запускается следующий git gc. Хотя трудно предсказать, когда это произойдет.


Все это приводит к последнему правилу: не беспокойтесь о названиях ветвей, пока не захотите одно

Посмотрите на нашу диаграмму где у них было два имени ветви, а у нас было одно:

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

Нам не нужно наше собственное имя ветви develop здесь. Нам нужен только один, если мы хотим добавить коммиты в конец . Мы можем сделать один:

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

и затем сделать новые коммиты:

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

Теперь нам нужно отправить наши новые коммиты на их, для которых мы используем git push. Это работает так же, как git fetch: мы предлагаем им коммиты, которые у нас есть, а не sh ID. Но это заканчивается по-другому. Отправив им наши коммиты M-N, мы просим их установить имя ветви develop, чтобы указать на коммит N. Если они принимают, мы обновляем наш собственный origin/develop:

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

Commit L больше не имеет никакого имени, указывающего на него, поэтому мы можем выправить излом на чертеже. Но мы можем также вернуться к нашему имени master и delete our develop:

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

Коммиты все еще там. Мы находим их, используя имя origin/develop. Больше нет причин искать их по имени develop. Поэтому, как только мы закончим с этим, мы просто перестанем его использовать и удалим. Затем, если они добавляют больше коммитов и мы git fetch, единственное имя, которое у нас есть, автоматически обновляется:

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

Если мы обнаружим, что нам нужно добавить больше коммитов, мы git checkout develop снова создайте наше имя develop из нашего origin/develop:

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

, и мы готовы добавить новые коммиты, а затем git push, как обычно.

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

Мы можем даже посмотреть на их коммиты, используя Git ' detached HEAD mode. Предположим, что мы нажали O и удалили наш develop, чтобы у нас было:

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

Теперь они добавляют новый коммит P. Мы используем git fetch, чтобы получить его:

...--G--H--I--J   <-- master (HEAD), origin/master
         \
          K--L--M--N--O--P   <-- origin/develop

Мы можем git checkout origin/develop сейчас. Поскольку origin/develop не является именем ветви - это имя удаленного отслеживания - наш Git будет использовать режим detached HEAD . В этом режиме специальное имя HEAD просто содержит необработанный га sh ID коммита, который мы просматриваем:

...--G--H--I--J   <-- master, origin/master
         \
          K--L--M--N--O--P   <-- HEAD, origin/develop

Если мы сделаем новый коммит Q здесь, имя HEAD продвигается, чтобы указать на наш новый коммит:

...--G--H--I--J   <-- master, origin/master
         \
          K--L--M--N--O--P   <-- origin/develop
                          \
                           Q   <-- HEAD

, и теперь мы действительно должны сделать имя ветви, чтобы запомнить ha sh ID Q, потому что если мы переключитесь с этого коммита (скажем, на P или J), мы забудем идентификатор ha sh. Кто может помнить эти вещи? Ну, Git может их запомнить. Нам просто нужно создать имя . Вот для чего нужны имена ветвей: запомнить последний коммит . Если Q будет последним коммитом, мы создадим для него новое имя. Мы можем называть его как угодно:

git checkout -b feature

и теперь имеем:

...--G--H--I--J   <-- master, origin/master
         \
          K--L--M--N--O--P   <-- origin/develop
                          \
                           Q   <-- feature (HEAD)

Операция git checkout -b создает выбранное нами имя и присоединяет HEAD к имени , Коммит, который мы выбрали, был коммитом, который мы использовали: тот, на который HEAD указывал напрямую. Теперь HEAD присоединено к новому имени, feature, а имя - имя ветви - указывает на коммит.

Обычно вы создаете имя, указывающее сначала P, затем обязуется сделать Q. Но если вы забудете, вот как вы восстанавливаетесь: git status говорит отсоединенная ГОЛОВА и вы говорите себе: упс, я должен создать имя ветви сейчас. Вы запускаете checkout -b или в Git 2.23 и более поздних версиях git switch -c, чтобы сделать это.

Заключение

Ваши имена ветвей предназначены для запоминания last-commit ha sh Идентификаторы. Создайте их, когда вы этого хотите. В противном случае, не связывайтесь с именами. Используйте опцию prune , чтобы убрать мертвые origin/* имена.

Ваш Git хочет использовать хотя бы одно имя, так что вы можете позволить этому сделать это: пусть он использует master, например. Затем сделайте перемотку вперед после git fetch. Если вы на самом деле никогда не выполняете свою собственную работу в репозитории, вы просто используете master и позволяете git merge --ff-only origin/master перенести вас на их обновление.

Или вы можете даже использовать режим detached-HEAD: git checkout origin/master, затем удалите имя master. Тебе это не нужно. Отдельное имя HEAD и имя удаленного отслеживания будут использоваться. После того, как git fetch обновит ваш origin/master, вы можете просто git checkout origin/master снова переместить отсоединенную ГОЛОВКУ. Это может удивить некоторых Git пользователей, поэтому, если вы используете этот подход, и кто-то другой захочет взять на себя этот Git репозиторий, вы можете предупредить их, но ваш Git репозиторий предназначен для вас не для них.

0 голосов
/ 28 января 2020

Что вы описываете, так это то, как я работаю над всеми моими проектами. [Ладно, это не совсем так, так что читайте дальше.]

В этом сценарии я на самом деле не «сотрудничаю» с собой. За один раз отвечает только один компьютер, часто (как вы говорите) по несколько дней; затем я переключаюсь обратно на другой компьютер. Основная реальность обычно в том, что я путешествую. Перед путешествием я переключаю «мастерство» на ноутбук; когда я возвращаюсь домой, я переключаю «мастерство» обратно на настольный компьютер.

В этой схеме я использую github исключительно в качестве посредника; репозиторий там приватный (до того, как github разрешил бесплатные приватные репозитории, я использовал для этого bitbucket). Ну, не исключительно ; также очень приятно иметь удаленный пульт дистанционного управления на случай, если меня или мой компьютер "сбьет шина".

Поэтому я бы сказал: да, делайте то, что вы описываете.

Теперь, что касается подразумеваемого вопроса в

, я просто хочу полное обновление. Все старые ветви удалены, все новые ветви удалены, et c.

... Путь к pu sh всем филиалам на удаленной линии в одной строке просто git push --all, но Что касается вытягивания, нет, точно не существует однострочной версии этого - по крайней мере, не для того, что я подозреваю, вы подразумеваете под этим . Даже создание нового клона не является одной из версий этого. Когда вы делаете клон или вытягиваете все, вы получаете весь репозиторий, включая все удаленные ветви; но локальные ветви, соответствующие удаленным ветвям, не создаются автоматически. Вот почему есть вопросы и ответы о переполнении стека, подобные этому:

Как получить все Git ветви

... и этот:

Может ли "git pull --all" обновить все мои локальные филиалы?

Так что, если вы счастливы делать то, что рекомендовано в ответах на эти вопросы, вот ваше обновление O (1).


Сноска: Вспомните, что я сказал "это не совсем так". У меня есть другой способ работы. Таким образом, я syn c папка рабочего дерева между двумя компьютерами, используя Finder или rsync (я использую Mac) в качестве посредника. Я все еще использую GitHub в качестве автономной резервной копии, но я передаю мастерство с одного компьютера на другой, просто выполняя syn c. На самом деле я мог бы использовать копию Finder во время передачи, но большую часть времени я использую программное обеспечение syn c. В этом нет никаких трудностей, поскольку репозиторий git - это просто набор папок и файлов, как и любой другой: он прекрасно синхронизирует / копирует с одного компьютера на другой. Таким образом, вы делаете все локальные ветви, потому что все локальные git просто копируются с одного компьютера на другой.

0 голосов
/ 23 января 2020

Итак, если я правильно понимаю ваш вопрос, вы можете использовать:

git fetch [remote]

для получения изменений с удаленного компьютера, но не для обновления веток отслеживания; или

git fetch --prune [remote]

для удаления ссылок, которые были удалены из удаленного репозитория.

Также просмотрите:

git pull [remote]

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

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