Что именно делает git fetch?

Edit: я проверил это что означает FETCH_HEAD в Git? прежде чем задавать вопрос.
Извините за оригинальный неточный вопрос.

мой вопрос в том, как fetch действительно работает? Fetch отбрасывает весь текущий журнал?

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

git status
git add .
git commit -m message1
git fetch origin
git reset head
git status
git add .
git commit -m message
git push

но после сброса кажется, что моя предыдущая фиксация (с message1) ушел.

это нормально или что-то не так?
Как я могу получить доступ к моей местной истории?
Они синхронизированы, но моя местная история исчезла.

старый персонал, забудьте об этом: я недавно изучал git CLI.
Кто-то велел мне напечатать:"git fetch head" для отслеживания удаленных филиалов.
Но интересно, что это делает ? Эта команда переопределяет мой локальный журнал?
И в чем разница между "git fetch" и "git fetch head" ?

2 ответов


git fetch сам по себе довольно прост. Сложных деталей до и после.

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

Commits, индекс и дерево труда!--144-->

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

обратите внимание, что каждый репозиторий имеет только один индекс, по крайней мере вначале. Этот индекс связан с one работа-дерево. В новых версиях Git, вы можете использовать git worktree add для добавления дополнительных рабочих деревьев; каждое новое рабочее дерево поставляется с одним новым индексом/промежуточной областью. Точка этого индекса должна выступать в качестве промежуточного держателя файла, расположенного между " текущей фиксацией "(aka HEAD) и дерево работы. Изначально HEAD commit и индекс обычно совпадает: они содержат одинаковые версии всех зафиксированных файлов. Git копирует файлы из HEAD в индекс, а затем из индекса в дерево работы.

легко увидеть дерево работы: в нем есть ваши файлы в обычном формате, где вы можете просматривать и редактировать их со всеми обычными инструментами на вашем компьютере. Если вы пишете код Java или Python или HTML для веб-сервера, файлы рабочего дерева могут использоваться компилятором, интерпретатором или веб-сервером. Файлы, хранящиеся в индексе и хранящиеся в каждом git commit, do не имеют эту форму и являются не используется компиляторами, интерпретаторами, веб-серверами и т. д.

еще одна вещь, которую нужно помнить о фиксациях, - это то, что как только файл находится в фиксации, он невозможно изменить. Никакая часть любого обязательства не может измениться. Таким образом, фиксация является постоянной-или, по крайней мере, постоянной, если она не удалена (что может быть сделано, но трудно и обычно нежелательно). Однако то, что находится в индексе и дереве работы, может быть изменено в любое время. Вот почему они существуют: индекс почти является "изменяемой фиксацией" (за исключением того, что он не сохраняется, пока вы не запустите git commit), а дерево работы сохраняет файлы в том виде, в котором может использовать остальная часть компьютера.1


1не надо и индекс и работа-дерево. VCS может лечить дерево работы как "изменяемая фиксация". Вот что делает Mercurial; вот почему Mercurial не нуждается в индексе. Возможно, это лучший дизайн,но это не так, как работает Git, поэтому при использовании Git у вас есть индекс. Наличие индекса-это большая часть того, что делает Git таким быстрым: без него Mercurial должен быть очень умным и все еще не таким быстрым, как Git.


коммиты помнят своего родителя; новые коммиты-это дети

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

но Git также сохраняет в новом коммите хэш ID текущего коммита. Мы говорим, что новая фиксация "указывает назад" на текущую фиксацию. Рассмотрим, например, этот простой репозиторий с тремя фиксациями:

A <-B <-C   <-- master (HEAD)

здесь мы говорим, что филиала master "указывает на" третий коммит, который я обозначил C, а не использовать один из непонятных идентификаторов хэша Git, таких как b06d364.... (Имя HEAD относится к имени филиала, master. Вот как Git может повернуть строку HEAD в правильный хэш ID: Git следует HEAD to master, затем считывает идентификатор хэша из master.) Это commit C сам, что "указывает на" - сохраняет хэш-идентификатор-commit B, однако; и совершить B точки для фиксации A. (С момента совершения A является самым первым фиксацией когда-либо, нет более раннего фиксации для него, чтобы указать, поэтому он вообще никуда не указывает, что делает его немного особенный. Это называется root commit.)

сделать новая commit, git упаковывает индекс в снимок, сохраняет его с вашим именем и адресом электронной почты и так далее, и включает хэш-идентификатор commit C, чтобы сделать новую фиксацию с новым хэш-идентификатором. Мы будем использовать D вместо нового хэш-идентификатора, поскольку мы не знаем, каким будет новый хэш-идентификатор:

A <-B <-C <-D

обратите внимание, как D указывает на C. Теперь что сюда изменяет идентификатор хэша, хранящийся под именем master, в магазине Dхэш ID вместо C. Имя, сохраненное в HEAD сам по себе совсем не меняется: он все еще master. Итак, теперь у нас есть это:

A <-B <-C <-D   <-- master (HEAD)

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

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

A--B--C--D   <-- master (HEAD)

распределенные репозитории: какие git fetch тут

при использовании git fetch, мы два разных Gits, с другой-а по теме-хранилищ. Предположим, у нас есть два репозитория Git на двух разных компьютерах, которые начинаются с тех же трех commits:

A--B--C

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

теперь вы, в вашем Git и вашем репозиторий, добавили новый commit D. Тем временем они-кто бы они ни были-возможно, добавили свои собственные новые обязательства. Мы будем использовать разные буквы, так как их коммиты обязательно будут иметь разные хэши. Мы также рассмотрим это в основном с вашей (Гарри) точки зрения; мы будем называть их "Салли". Мы добавим еще одну вещь к нашей картине код репозиторий: теперь он выглядит так:

A--B--C   <-- sally/master
       \
        D   <-- master (HEAD)

теперь предположим, что Салли сделала два совершает. В ее хранилище она это:

A--B--C--E--F   <-- master (HEAD)

или, может быть (если она принесет от вас, но еще не побежала git fetch):

A--B--C   <-- harry/master
       \
        E--F   <-- master (HEAD)

, когда вы выполнить git fetch, вы подключаете свой Git к Git Салли и спрашиваете ее, есть ли у нее новые коммиты, добавленные в ее master С момента совершения C. Она делает-у нее есть ее новые коммиты E и F. Так что ваш Git получает эти коммиты от нее, вместе со всем необходимым для завершения снимков для этих коммитов. Затем ваш Git добавляет эти коммиты в код репозитория, так что теперь у вас есть это:

        E--F   <-- sally/master
       /
A--B--C
       \
        D   <-- master (HEAD)

Как видите, что git fetch неужели для вас было собрать все ее новая совершает и добавить их в свой репозиторий.

для того, чтобы вспомнить, где ее master теперь, когда вы поговорили с ней Git, ваш Git копирует ее master to код sally/master. Твой собственный!--24--> и свой HEAD, не менять вовсе. Только эти имена "памяти другого репозитория Git", которые git называет удаленное отслеживание названия ветвей, изменить.


2этот хэш является криптографическим хэшем, отчасти поэтому трудно обмануть Git, и отчасти потому, что криптографические хэши естественно ведут себя хорошо для целей Git. Текущий хэш использует SHA-1, который был безопасный, но видел атаки грубой силы и теперь оставлен для криптографии. Git, скорее всего, перейдет на SHA2-256 или SHA3-256 или какой-либо другой более крупный хэш. Будет переходный период с некоторыми неприятностями. :-)


теперь вы должны объединить или перебазировать -git reset это вообще не так

обратите внимание, что после того, как вы получили от Салли, это код репозиторий, и только ваш репозиторий, который имеет всю работу от вас обоих. У Салли все еще нет вашего нового commit D.

это все еще верно, даже если вместо "Салли" ваш другой Git называется origin. Теперь, когда у вас есть оба!--24--> и origin/master, вы должны сделать что-то, чтобы подключить новый commit D С их последнего коммита F:

A--B--C--D   <-- master (HEAD)
       \
        E--F   <-- origin/master

(я переехала D сверху по причинам рисования графика, но это тот же график, что и прежде чем,

ваши основные два варианта здесь, чтобы использовать git merge или git rebase. (Есть и другие способы сделать это, но это два, чтобы узнать.)

слияние на самом деле проще как git rebase делает что-то, что включает в себя глагольную форму слияния,слияние. Что?!--71--> делает это, чтобы запустить форму глагола слияния, а затем зафиксировать результат как новая commit, который называется объединить commit или просто "сольют", которых является существительное, образованное от слияния. Мы можем нарисовать новый commit слияния G таким образом:

A--B--C--D---G   <-- master (HEAD)
       \    /
        E--F   <-- origin/master

в отличие от обычного коммита, a объединить commit и два родителей.3 он подключается к обоим из двух предыдущих коммитов, которые были использованы для слияния. Это позволяет подтолкнуть ваш новый commit G to origin: G берет с собой ваш D, но также подключается обратно к их F, так что их Git Хорошо с этим новым обновлением.

это слияние - такое же слияние, которое вы получаете от слияния двух ветвей. И в самом деле, ты!--136-->сделал объединить две ветви здесь: вы объединили свой master С Салли (или origin ' s)master.

используя git rebase обычно легко, но это сложнее. Вместо слияние ваш commit D С их фиксацией F сделать новый объединить commit G, что git rebase это скопировать каждый из ваших коммитов, так что новый копии, которые являются новыми и разными фиксациями, приходят после последней фиксации на вашем вверх.

здесь ваш апстрим origin/master, и совершает, что у вас есть, что они не только совершают D. Так что git rebase делает скопировать of D, который я называю D', размещение копии после их фиксации F, так что D''родитель ы F. Промежуточный график выглядит так:5

A--B--C--D   <-- master
       \
        E--F   <-- origin/master
            \
             D'   <-- HEAD

процесс копирования использует тот же код слияния, что и git merge использует для выполнения глагольной формы,слияние, ваших изменений от commit D.4 как только копия сделана, однако, код rebase видит, что больше нет коммитов для копирования, поэтому он тогда изменения код master ветке точки в конечную скопировал совершить D':

A--B--C--D   [abandoned]
       \
        E--F   <-- origin/master
            \
             D'   <-- master (HEAD)

это отказывается от первоначальной фиксации D.6 это означает, что мы можем прекратить рисовать его тоже, так что теперь мы получаем:

A--B--C--E--F   <-- origin/master
             \
              D'   <-- master (HEAD)

теперь это легко git push ваш новый commit D' на origin.


3в Git (но не Mercurial) фиксация слияния может иметь более двух родителей. Это не делает ничего, что вы не можете сделать путем повторного слияния, поэтому это в основном для хвастовства. :-)

4технически, фиксация базы слияния, по крайней мере для этого случая, является фиксацией C и два наконечника фиксации D и F, так что в этом случае это буквально то же самое. Если вы перебазировать больше, чем один коммит, это становится немного сложнее, но в принципе это все равно просто.

5это промежуточное состояние, где HEAD отсоединяется от master, обычно невидимы. Ты увидишь это, только если что-то идет не так во время глагольной формы слияния, так что Git останавливается и должен получить от вас помощь, чтобы закончить операцию слияния. Когда тут возникают, когда есть конфликт слияния во время перебазирования-важно знать, что Git-это в "отрезанная голова" государства, но пока перебазировать выполняет самостоятельно, вам не придется заботиться об этом так много.

6исходная цепочка фиксации временно сохраняется через Git reflogs и через имя ORIG_HEAD. The ORIG_HEAD значение перезаписывается следующей операцией, которая вносит "большие изменения", и запись reflog в конечном итоге истекает, как правило, через 30 дней для этой записи. После этого git gc действительно удалит исходную цепочку фиксации.


на


вам не нужно делать два отдельных коммита, и git fetch не будет ронять журнал.

 --o--o--o (origin/master)
          \
           x--x (master: my local commits)

что вы должны сделать, это перебазировать локальную фиксацию поверх любой новой фиксации, полученной :

git fetch

--o--o--o--O--O (origin/master updated)
         \
          x--x (master)

git rebase origin/master

--o--o--o--O--O (origin/master updated)
               \
                x'--x' (master rebased)

git push

--o--o--o--O--O--x'--x' (origin/master, master)

даже проще, с Git 2.6, я бы использовал config:

git config pull.rebase true
git config rebase.autoStash true

и git pull будет автоматически воспроизводить ваши локальные коммиты поверх origin/master. Тогда вы можете git push.