Способ помнить и думать об этом - который немного противоречит самому себе, но в конце концов это работает, - помнить, что Git не работает с файлами .Git работает с коммитами .
Конечно, коммиты содержат файлов, но файлы, которые содержит коммит Git, находятся в специальном, только для чтения, только для Git, сжатый формат (иногда очень сжатый), потому что каждый коммит содержит все файлы, как полный снимок всех файлов, которые вы сохранили в этом коммите в то время, когда вы сделали этот коммит.Мне нравится говорить, что файлы внутри коммитов имеют формат сублимационной сушки, потому что они не могут быть использованы для чего-либо без предварительной регидратации.
Сублимированные файлы могут тоже не изменилось .Это означает, что они могут свободно совместно использоваться из одного коммита в любой другой: нет риска изменения файла фиксации в старом коммите, поэтому новый коммит может просто повторно использовать его напрямую.Также нет риска, что измененный файл будет изменен в более новом коммите, так что любой даже более новый коммит может продолжить повторное использование старого файла, высушенного вымораживанием, и так далее.Тот факт, что вы (и Git) буквально не можете изменить что-либо , когда-либо , около любого существующего коммита, означает, что каждый коммит отлично подходит для архивирования.Но это также означает, что каждый коммит сам по себе совершенно бесполезен для выполнения какой-либо реальной работы, поскольку для выполнения любой реальной работы вам нужно изменить - или добавить и / или удалить - некоторые файлы, и вы не можете сделать это в любом коммите.
Git, следовательно, дает вам work-tree .Рабочее дерево, довольно просто, где вы делаете свою работу.Рабочее дерево не хранится в собственном хранилище и не переносится из одного клона в другой: каждый клон имеет свое собственное личное рабочее дерево.
Когда вы впервые создаете новый клон из какого-либо существующего хранилища Git, ваш Git связывается с другим Git, получает список всех его коммитов и копирует все этих коммитов.Поскольку коммиты являются историей, теперь у вас есть каждый когда-либо зафиксированный файл, и вся история (коммитов) показывает, что, например, коммит G
является родителем коммита H
, 1 и G
имеют родителя F
и так далее.В любом случае, в настоящее время у вас есть нет файлов в новом пустом рабочем дереве, которое Git создал, чтобы у git clone
было место для размещения рабочих файлов, но есть last commitна master
, который теперь может извлечь ваш Git.
Чтобы заполнить пустое рабочее дерево, Git теперь возьмет все высушенные сублимацией файлы в снимке H
на кончике master
и увлажнить их.Воссозданные, обычные, повседневные файлы, которые вы можете видеть и работать с ними, теперь есть в вашем рабочем дереве, и вы приступаете к выполнению некоторой работы.
К перенесите вашу новую работу в какой-нибудь другой Git-репозиторий, однако вы должны упаковать все как новый коммит.Но Git не делает коммиты из дерева работ.Вместо этого Git делает новые коммиты из того, что Git вызывает, по-разному: index , область подготовки или (иногда особенно в очень старой документации Git) кэш .
Что такое индекс, это своего рода промежуточная область - отсюда и второе имя - которая находится между вашей текущей фиксацией (которую мы называем H
) и вашей работой-дерево.Внутри коммита ваши файлы высушены и не могут быть изменены.Однако в индексе есть дополнительная копия этих файлов, и, в отличие от копии в рабочем дереве, эту копию можно изменить - ну, на самом деле заменила .Таким образом, изменив файлы обычного формата в вашем рабочем дереве, теперь вы должны запустить git add
для каждого файла, чтобы высушить его и скопировать новый высушенный файл в область index / staging-area.Новая копия заменяет старую.
Если выВозьмите совершенно новый файл, вы можете git add
it: заморозить его, скопировать в индекс, и теперь есть новый файл в области index / staging, рядом со всеми существующими файлами в индексе, готов идти.И, вы можете полностью удалить файл, из и индекс / промежуточную область и рабочего дерева, используя git rm
.
В любом случае, как только вы обновите (или удалите) все индексные копии каждого обновленного или нового (или удаленного) файла, вы готовы к запуску git commit
.Это берет все индексные файлы - все они уже в специальном замораженном формате - и снимает их в новый коммит.Поскольку это новый коммит, он получает новый и совершенно уникальный для вас хэш-идентификатор, который нет другой Git где-нибудь прямо сейчас, но мы 'Я просто назову это commit I
. родитель нового коммита I
- это существующий коммит H
, который вы извлекли для создания нового коммита I
.И теперь, когда вы сделали новый коммит I
, Git может обновить ваше имя master
, чтобы запомнить фактический хэш-идентификатор I
.Поскольку I
запоминает фактический хэш-идентификатор H
, мы говорим, что master
указывает на I
, а I
указывает на H
, и мыможно нарисовать это:
... <-F <-G <-H <-I <-- master
Хороший способ думать об индексе состоит в том, что он содержит следующий сделанный вами коммит .Вы манипулируете им с помощью git add
и git rm
(и иногда git reset
).Когда вы git checkout
делаете коммит, Git заполняет индекс из коммита, который вы выписали.Когда вы делаете новый коммит, Git навсегда замораживает все файлы, которые находятся в индексе прямо тогда , в новый коммит.
Обратите внимание, что индекс, как и рабочее дерево, является приватным это Git хранилище.Он никогда не будет передан в любой другой Git-репозиторий.Передаются только коммиты - ну, коммиты и (аннотированные) теговые объекты и файлы, которые находятся внутри коммитов (которые могут быть помечены).В любом случае индекс и рабочее дерево являются частными; коммиты являются общими.Это, опять-таки, является частью того, почему Git все о коммитах .
1 Это не настоящие хеш-идентификаторы.Хэш-идентификаторы реального коммита - это большие, ужасно выглядящие случайные вещи типа 83232e38648b51abbcbdb56c94632b6906cc85a6
.Мне нравится использовать заглавные буквы из одной заглавной буквы, чтобы говорить о Git, потому что люди могут относиться к ним.Очевидная проблема с одиночными заглавными буквами заключается в том, что они заканчиваются слишком быстро для реального репозитория, который может легко иметь много тысяч коммитов.Но они просты для простых примеров.
Перенос коммитов из Git в Git
Теперь, когда вы сделали новый коммит I
, вы можете отправить это где-то еще, к другому Git.Ваш Git вызовет их Git и скажет: Я совершил, у вас это есть? Они, конечно, скажут "нет", и ваш Git сможет отправить им коммит со всеми его файлами.Теперь они делают с новым коммитом, используя тот же хэш-идентификатор.Они обновят одно из их имен, возможно, их master
имя ветки, так что они также смогут запомнить реальный фактический большой уродливый идентификатор хеша.Если их master
имя помнили H
ранее, они могут безопасно переключиться на I
, потому что I
помнит H
как родителя I
.
Этот же процесс работает в другом направлении: если вы клонировали из какого-то репозитория в другом месте, скажем, на GitHub, вы можете заставить свой Git снова вызвать этот репозиторий и выяснить, какие коммиты у них есть, а у вас нет.Затем ваш Git загрузит эти коммиты - с содержащимися в них сублимированными файлами, да, но он не загружает файлов , он загружает коммитов .
Давайтескажем, что вы отправили им свой I
, и с тех пор они добавили свои собственные J
и K
:
...--F--G--H--I <-- master
\
J--K <-- [received from them as their master]
Ваш собственный Git запомнит хэш-идентификатор K
записав его в имя для удаленного слежения origin/master
.
Вы можетеТеперь перенесите ваш Git из вашего (теперь совместно используемого) коммита I
в теперь общий для общего доступа, сделанный ими коммит K
, если хотите.Для этого вы можете git checkout
зафиксировать его по хеш-идентификатору или по имени origin/master
.Вы также можете объединить ваш master
- снова / снова указывать на теперь общий I
- с их коммитом K
.Поскольку нет новых собственных коммитов после I
, ваш Git может сделать это, выполнив операцию fast-forward вместо реального слияния.Это на самом деле равно: проверить коммит K
и присвоить имени master
указатель на K
.
То есть после git merge origin/master
ваш Git обновит вашследующий график:
...--F--G--H--I--J--K <-- master, origin/master
и в вашем рабочем дереве будет зафиксирован коммит K
.
Переключение коммитов иногда означает удаление рабочего деревафайлы
Теперь предположим, что для перехода от I
к K
тот, кто сделал J
, а затем K
удалил связкуфайлы, используя git rm
, чтобы сказать Git: удалить эти файлы как из index / staging-area, так и из дерева работы. Скажем, кто бы ни сделал J
, сделал:
git rm file1 file2 file3
git commit -m "make commit J"
Эти три файла в коммите I
(потому что они изначально вышли из коммита I
).Они все еще в коммите I
, потому что ни один из существующих коммитов не может быть изменен.Но они не в коммите J
, потому что после удаления их из индекса был сделан новый коммит J
.
Затем они удалили еще несколько файлов:
git rm file4 file5
git commit -m "make commit K"
То есть file4
и file5
равны в коммитах I
и J
, но не в коммите K
.
Когда у вас есть переключатель Git с H
на K
, ваш Git, следовательно, удалит все пять файлов из вашего рабочего дерева.Они безопасно хранятся в I
и надежно (и навсегда!) , а не - хранятся в K
.Вернитесь к J
и два из пяти файлов вернутся;вернитесь к I
, а остальные три вернутся;перейдите к K
и они все уйдут.Это нормально, хотя: они все еще там, в коммите I
, а Git - это все о коммитах .
неотслеживаемых файлов
Теперь тот факт, что вы иметь рабочее дерево, в котором хранятся обычные файлы, не относящиеся к Git, в их обычном формате без сублимации, что означает, что вы можете в любое время создавать файлы, которые вы никогда не git add
и git commit
.Эти файлы существуют в вашем рабочем дереве, но Git не знает о них.Поскольку их нет в index , они являются неотслеживаемыми файлами .
Но как насчет файлов, таких как file1
- file5
?Они были в индексе в какой-то момент, когда мы сделали коммит I
, и их нет, если мы сейчас зафиксировали K
.Если вы создадите new file1
прямо сейчас, этот файл будет существовать в рабочем дереве и не будет отслеживаться.Но если вы вернетесь к фиксации I
, Git придется перезаписать неотслеживаемое рабочее дерево file1
с помощью file1
, которое он восстанавливает из фиксации I
.Затем, когда вы вернетесь к K
, Git удалит регидратированный file1
.file1
, которое у вас было в вашем рабочем дереве, когда у вас было K
, теперь ушло , а вообще нет копии этого файла в Git .
Это опасность случайного отслеживания (добавления в индекс) файла, который вы никогда не хотели отслеживать.Теперь он постоянно хранится в коммитах, которые вы сделали из этого индекса в то время.Если вы извлекаете один из этих коммитов, файл отслеживается, поскольку он копируется в ваш индекс.Если затем вы перемещаете с , который фиксирует тот, где файл не существует, он удаляется из вашего индекса и из вашего рабочего дерева , и этогде боль приходит.
ThРезюме TL; DR: будьте осторожны с тем, что вы делаете, потому что, если вам нужно что-то удалить, вы устанавливаете эти маленькие бомбы замедленного действия для других людей или даже для себя, если вы забыли об этом. Не отслеживаемые файлы не хранятся в Git, но файлы с таким же именем могут существовать в каком-то другом коммите, и если вы извлечете такой коммит, он может перезаписать ваши неотслеживаемые файлы.
Если вам нужны, например, конфигурации прототипов, где вы хотите показать, как что-то настраивать, без фактической настройки, назовите эти вещи так, чтобы они не вступали в конфликт с фактической конфигурацией , если это не является частью хранилища. Например, вместо .htaccess
вы можете зафиксировать .htaccess.example
. Тогда все в порядке, чтобы этот файл был в каждом коммите, а сам .htaccess
никогда не был в любом коммите. Git никогда не удалит активный файл рабочего дерева .htaccess
(он не зафиксирован и не находится в каком-либо историческом коммите), и обновит файл .htaccess.example
только при переключении с коммита на коммит.