Что делает git fetch точно?

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

Мой вопрос: как работает 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

Но после reset кажется, что мой предыдущий commit (с message1) ушел.

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

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

Ответ 1

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

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

Задает, индекс и дерево работы

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

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

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

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


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


Записывает свой родитель; новые коммиты - это дети

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

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

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

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

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

A <-B <-C <-D

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

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

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

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

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

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

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

A--B--C

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

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

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 к Sally Git и спросите ее, есть ли у нее какие-либо новые коммиты, добавленные к ней master с фиксацией C. Она делает - у нее есть новые коммиты E и F. Таким образом, ваш Git получает те коммиты от нее, а также все необходимое для завершения снимков для этих коммитов. Затем ваш Git добавляет эти коммиты в ваш репозиторий, так что теперь у вас есть это:

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

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

Чтобы запомнить, где находится ее master, теперь, когда вы поговорили с ней Git, ваш Git копирует своего хозяина на ваш sally/master. Ваш собственный master и ваш собственный HEAD не меняются вообще. Только эти "воспоминания о других репозиториях Git", которые Git вызывает имена удаленных треков, изменяются.


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


Теперь вы должны объединиться или rebase- git reset, как правило, неверно

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

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

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

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

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

Слияние действительно проще, поскольку git rebase делает что-то, что связано с глагольной формой слияния, для слияния. То, что git merge делает, - это запустить глагольную форму слияния, а затем зафиксировать результат как новую фиксацию, которая называется фиксацией слияния или просто "слияние", которая является существительной формы слияния. Мы можем сделать новое объединение слиянием G следующим образом:

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

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

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

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

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

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

В процессе копирования используется тот же код слияния, который git merge использует для выполнения формы глагола, чтобы объединить ваши изменения с commit D. 4 После того, как копия завершена, код восстановления видит, что копий больше нет, поэтому он изменяет ветвь master, чтобы указать на окончательное скопированное commit 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 находится в этом состоянии "отсоединенный HEAD", но до тех пор, пока rebase завершается самостоятельно, вам не нужно заботиться об этом так много.

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


Команда git pull запускает git fetch, а затем вторую команду

Обратите внимание, что после git fetch вам обычно нужно запустить вторую команду Git либо git merge, либо git rebase.

Если вы заранее знаете, что вы наверняка сразу же воспользуетесь одной из этих двух команд, вы можете использовать git pull, который запускает git fetch, а затем запускает одну из этих двух команд. Вы выбираете, какую вторую команду запускать, устанавливая pull.rebase или поставляя --rebase в качестве параметра командной строки.

Пока вы не знакомы с тем, как работают git merge и git rebase, я предлагаю не использовать git pull, потому что иногда git merge и git rebase не могут выполняться самостоятельно. В этом случае вы должны знать, как справиться с этим сбоем. Вы должны знать, какую команду вы на самом деле выполняли. Если вы запустите команду самостоятельно, вы узнаете, какую команду вы запустили, и где искать помощь, если необходимо. Если вы запустите git pull, вы можете даже не знать, какую вторую команду вы выполняете!

Кроме того, иногда вам может понадобиться посмотреть, прежде чем запускать вторую команду. Сколько коммитов принесли git fetch? Сколько будет работы по слиянию и переустановке? Сливается лучше, чем перебазировать прямо сейчас, или лучше восстанавливается, чем слияние? Чтобы ответить на любой из этих вопросов, вы должны отделить шаг git fetch от второй команды. Если вы используете git pull, вы должны заранее решить, какую команду выполнить, прежде чем вы даже узнаете, какой из них будет использовать.

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

Ответ 2

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

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

Что вам нужно сделать, это переустановить локальный коммит поверх любого нового коммита, полученного командой git fetch:

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, я бы использовал конфигурацию:

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

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