Являются ли «совершенные» и «неизмененные» одинаковыми? - PullRequest
2 голосов
/ 27 апреля 2019

Я читал о Трех Состояниях в Git от https://git -scm.com / book / en / v2 / Начало работы - Что такое Git% 3F Здесь говорится, что Git имеет три основных состояния , в которых могут храниться ваши файлы: зафиксировано , изменено и организовано .

Затем я также прочитал о двух состояниях: отслеживается или не отслеживается из https://git -scm.com / book / ru / v2 / Git-Basics-Recording-Changes-to- заместитель Repository Здесь говорится, что каждый файл в вашем рабочем каталоге может находиться в одном из двух состояний : отслеженных или неотслеживаемых. Отслеживаемые файлы - это файлы, которые были в последнем снимке; они могут быть неизмененными , измененными или постановочными .

Сходны ли состояния , упомянутые в Трех состояниях , схожими с субсостояниями отслеживаемых файлов ? зафиксировано и неизменено то же самое?

Эти изображения показывают, что они одинаковы?

The lifecycle of the status of your files

The three file states for Git: modified, staged, and commited

Ответы [ 3 ]

3 голосов
/ 27 апреля 2019

TL; DR

Отслеживаемое состояние не является подмножеством перечисленных трех состояний, и перечисленных трех состояний недостаточно, чтобы описать (или понять, действительно), как работает Git.

Long

Эта вещь "трех состояний" является чем-то вроде белой лжи, поэтому, вероятно, на странице написано:

Git имеет три main состояния

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

На самом деле, здесь на самом деле происходит то, чтообычно три копии каждого файла.Одна копия находится в текущем коммите, средняя копия находится в области index / staging, а третья копия находится в вашем рабочем дереве.

Средняя копия - та, что в индексе - не необходимо с точки зрения системы контроля версий.Mercurial - это еще одна система управления версиями, которая очень похожа на Git, и у нее есть только две копии каждого файла: подтвержденная и рабочая.Эту систему гораздо проще думать и объяснять.Но по разным причинам 1 Линус Торвальдс решил, что у вас должна быть третья копия, зажатая между коммитом и рабочим деревом.

Полезно знать, что зафиксированные копии файловв специальном замороженном, только для чтения, сжатом формате Git-only (который Git называет blob-объектом , хотя вам не нужно знать это в большинстве случаев).Поскольку такие файлы заморожены / доступны только для чтения, Git может делиться ими с каждым коммитом, который использует одну и ту же копию файла.Это может сэкономить огромное количество дискового пространства: один коммит из файла размером десять мегабайт занимает до десяти мегабайт (в зависимости от сжатия), но делает второй коммит с тем же файлом, а новая копия занимает ноль лишних байтов: он просто повторно используетсуществующая копия.Независимо от того, сколько больше коммитов вы делаете, до тех пор, пока вы продолжаете повторно использовать старый файл, для его хранения не требуется больше места.Git просто продолжает использовать оригинал.

Фактически, все в коммите замораживается навсегда.Никакая часть какого-либо коммита - ни файл, ни информация об авторе, ни орфографическая ошибка в сообщении журнала - не может быть изменена.Лучшее, что вы можете сделать, это сделать новый и улучшенный другой коммит, который исправляет орфографическую ошибку или что-то еще.Тогда вы можете использовать новый и улучшенный коммит вместо старого и паршивого, но новый коммит - это другой коммит с другим хеш-идентификатором.Идентификаторы хэша - это истинные имена коммитов (и, в этом отношении, объектов BLOB-объектов, которые идут с моментальным снимком фиксации).

Таким образом, коммиты являются постоянными 2 и доступны только для чтения,Файлы внутри коммитов сжимаются в формат только для чтения, Git-only, freeze -ried.Поскольку коммиты являются историей, они сохраняют историю навсегда, на случай, если вам захочется оглянуться назад, чтобы увидеть, что кто-то сделал, когда и почему.Но это совсем не хорошо для выполнения любой реальной работы.Вам нужны файлы, чтобы они были податливыми, гибкими, пластичными, гибкими, замазанными в ваших рукахВам нужно работать с вашими файлами.Короче говоря, вам нужно рабочее дерево , где вы можете выполнять свою реальную работу.

Когда вы git checkout делаете коммит, Git извлекает лиофилизированные копии в это рабочее дерево.Теперь все ваши файлы там, где вы можете их использовать и изменять.Можно подумать, что git commit возьмет обновленные файлы из рабочего дерева и зафиксирует их - это, например, то, что делает hg commit в Mercurial, - но нет, это не то, что делает Git.

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

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


1 Большинство из этих причин сводятся к производительности. Git git commit в тысячи раз быстрее, чем Mercurial hg commit. Отчасти это связано с тем, что Mercurial написан в основном на Python, но во многом это связано с индексом Git.

2 Точнее, коммиты сохраняются до тех пор, пока никто не сможет найти их по хеш-идентификатору. Это может произойти, когда вы переключаетесь со старой и паршивой фиксации на новую и улучшенную копию. После этого старые и паршивые коммиты, если они действительно не обнаруживаются (в отличие от просто скрытых от случайного наблюдения), могут быть удалены Git's сборщиком мусора , git gc. * 1106. *


Для каждого файла проверьте его состояние в трех экземплярах

Вы уже выбрали некоторый коммит в качестве текущего (HEAD) коммита через git checkout. Git обнаружил, что этот коммит имеет некоторое количество файлов; он извлек их все в индекс и в рабочее дерево. Предположим, у вас есть только файлы README.md и main.py. Теперь они такие:

  HEAD           index        work-tree
---------      ---------      ---------
README.md      README.md      README.md
main.py        main.py        main.py

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

  HEAD           index        work-tree
---------      ---------      ---------
README.md(1)   README.md(1)   README.md(1)
main.py(1)     main.py(1)     main.py(1)

Это соответствует первому состоянию книги Pro Git.

Теперь вы модифицируете один из файлов в вашем рабочем дереве. (Это единственные файлы, которые вы можете видеть и работать с обычными не-Git командами.) Допустим, вы поместили версию 2 README.md в рабочее дерево:

  HEAD           index        work-tree
---------      ---------      ---------
README.md(1)   README.md(1)   README.md(2)
main.py(1)     main.py(1)     main.py(1)

Git теперь скажет, что у вас есть изменения, не подготовленные для коммита к README.md. На самом деле это означает, что если мы сделаем два сравнения - начиная с HEAD против индекса, затем перейдем к индексу против рабочего дерева - мы увидим то же самое в первом сравнении, другое во втором . Это совпадает с «измененным, но не подготовленным» состоянием книги Pro Git.

Если вы теперь запустите git add README.md, Git заморозит обновленную версию рабочего дерева-2 README.md и перезапишет ее в индексе:

  HEAD           index        work-tree
---------      ---------      ---------
README.md(1)   README.md(2)   README.md(2)
main.py(1)     main.py(1)     main.py(1)

Единственное небольшое тонкое изменение в таблице состоит в том, что теперь, при сравнении, HEAD -vs-index показывает, что README.md изменилось, в то время как index-vs-work-tree показывает, что они одинаковы. Git называет эту ситуацию изменениями для коммита . Это совпадает с состоянием «модифицированного и поэтапного» книги Pro Git.

Если вы сделаете новый коммит сейчас, Git упакует все, что находится в индексе прямо сейчас - то есть, версия main.py и версия 2 README.md - и сделать новый коммит, используя эти файлы. Затем он настроит все так, чтобы HEAD означало новый коммит, вместо того, который вы извлекли ранее. Итак, теперь, хотя в old commit все еще есть оба файла в форме версии 1, теперь у вас есть:

  HEAD           index        work-tree
---------      ---------      ---------
README.md(2)   README.md(2)   README.md(2)
main.py(1)     main.py(1)     main.py(1)

и теперь все три копии README.md соответствуют.

Но предположим, что вы изменили README.md в рабочем дереве, чтобы сделать версию 3, тогда git add, что:

  HEAD           index        work-tree
---------      ---------      ---------
README.md(1)   README.md(3)   README.md(3)
main.py(1)     main.py(1)     main.py(1)

Затем вы изменяете README.md еще немного, чтобы сделать версию 4, отличную от всех трех предыдущих версий:

  HEAD           index        work-tree
---------      ---------      ---------
README.md(1)   README.md(3)   README.md(4)
main.py(1)     main.py(1)     main.py(1)

Теперь, когда мы сравниваем HEAD -vs-index, мы видим, что README.md подготовлен для фиксации , но когда мы сравниваем индекс с рабочим деревом, мы видим, что он также не ставится на коммит . Это не соответствует ни одному из трех состояний, но возможно!

Отслеживается и не отслеживается

Отслеживаемые файлы - это файлы, которые были в последнем снимке ...

Это, к сожалению, вводит в заблуждение. Фактически, отслеживаемый файл очень просто любой файл, который сейчас находится в индексе . Обратите внимание, что индекс податлив. Возможно, прямо сейчас в нем есть README.md версия 3, но вы можете заменить README.md другой версией или даже удалить , что README.md полностью.

Если вы удалите , то README.md вы получите:

  HEAD           index        work-tree
---------      ---------      ---------
README.md(1)                  README.md(4)
main.py(1)     main.py(1)     main.py(1)

Версия 3 сейчас просто ушла . 3 Так что теперь README.md в рабочем дереве - это неотслеживаемый файл . Если вы поместите версию - любую версию - README.md обратно в индекс перед запуском git commit, README.md вернется к отслеживанию, потому что она находится в индексе.

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


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


Git делает новые коммиты из индекса; новые коммиты возвращаются к старым

Мы уже упоминали кое-что из этого выше, но давайте рассмотрим это снова, потому что это важно для понимания Git.

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

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

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

A <-B <-C

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

ВсеЭти указатели всегда указывают назад , по необходимости.Никакая часть какого-либо коммита не может измениться после того, как мы его сделаем, поэтому B может содержать идентификатор A, но мы не можем изменить A, чтобы заполнить идентификатор B в A.C может указывать на B, но мы не можем изменить B, чтобы указать на C.Но все, что нам нужно сделать, это запомнить реальный хэш-идентификатор C, и здесь имена ветвей входят в .

Давайте выберем имя master и сделаем Git save C хэш-идентификатор под этим именем.Поскольку имя содержит хэш-идентификатор, оно указывает на C:

A--B--C   <-- master

(Из-за лени и / или других причин я перестал рисовать соединители в коммитах в виде стрелок. Это нормально,потому что они не могут измениться, и мы знаем, что они указывают назад.)

Теперь давайте проверим коммит C, используя git checkout master, который заполняет наш индекс и рабочее дерево из файлов, сохраненных с коммитом C:

git checkout master

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

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

A--B--C   <-- master
       \
        D

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

A--B--C
       \
        D   <-- master

и теперь коммит D является последним коммитом.Все, что нам нужно запомнить, это имя master;Git запоминает хэш-идентификаторы для нас.

А как насчет git commit -a?

Git действительно может фиксировать все, что есть в вашем рабочем дереве, используя git commit -a.Но на самом деле это означает, что, по сути, запускается git add -u прямо перед выполнением коммита: для каждого файла, который на самом деле, в индексе, Git проверяет, является ли копия рабочего дереваотличается, и если это так, Git добавляет этот файл в индекс.Затем он делает новый коммит из индекса. 4

Эта промежуточная, третья копия каждого файла - того, что в индексе - это , почему вам нужно git add все время.Как новый пользователь Git, он в основном мешает вам.Соблазнительно обойти это с git commit -a и притвориться, что его не существует.Но это в конечном итоге оставляет вас в затруднительном положении, когда что-то не получается с проблемой с индексом, и оставляет файлы tracked-vs-неотслеживаемыми совершенно необъяснимыми.

Кроме того, наличие индекса допускает все виды изящных приемов, таких какgit add -p, которые на самом деле довольно полезны и практичны для некоторых рабочих процессов, поэтому неплохо узнать об индексе.Вы можете оставить большую часть этого на потом, но просто помните, что есть эта промежуточная лиофилизированная копия, и что git status выполняет два сравнения - HEAD -vs-index, затем index-vs-рабочее дерево - и все это имеет гораздо больше смысла.


4 Это тоже ложь в белом: Git фактически делает временный индекс дляэтот случай.Временный индекс начинается как копия реального индекса, а затем Git добавляет туда файлы.Однако, если с фиксацией все идет хорошо, временный индекс становится индексом - как бы реальным, основным индексом - поэтому добавление к временному индексу имеет тот же эффект.Единственный раз, когда это проявляется, - это когда завершается неудачей или, если вы достаточно хитры, когда вы входите и проверяете состояние хранилища , пока git commit -a все еще находится вprogress.

Картина становится еще более сложной, если вы используете git commit --only, что составляет два временных индекса (индекса?).Но давайте просто не будем идти туда.: -)

0 голосов
/ 27 апреля 2019

Commit c3e7fbc (май 2005 г., Git v0.99) - это первый экземпляр, где использовалось «немодифицированное», и иллюстрирует, что «немодифицированные» файлы являются кандидатами в diff, даже для переименовано файлы:

[PATCH] Перестройка Diff, добавление второй половины обнаружения копии.

Этот патч расширяет diff-cache и diff-files, чтобы сообщать о неизмененных файлах также в diff-core, когда действует -C (обнаружение копирования), так что немодифицированные файлы также могут использоваться в качестве исходных кандидатов.

Это отличается от первого вхождения термина uncommitted, который показывает, что такое "uncommitted": commit 219ea3a, сентябрь 2006 г., Git v1.5.3-rc0 .

gitk: показывать локальные незафиксированные изменения как фальшивый коммит

Если в репозитории есть локальные изменения, т. Е. git-diff-index HEAD производит какой-то вывод, тогда это необязательно отображает дополнительную строку на графике в качестве дочернего элемента фиксации HEAD (но с красным кружком, указывающим, что это не реальный коммит).
В окне настроек есть флажок, чтобы контролировать, делает ли Gitk это или нет.

Это включало комментарий как:

# tree has COPYING.  work tree has the same COPYING and COPYING.1,
# but COPYING is not edited.  
# We say you copy-and-edit COPYING.1;
# this is only possible because -C mode now reports the unmodified
# file to the diff-core.

Uncommitted остается более общим термином при работе с отслеживаемым элементом.
Чуть позже commit 6259ac6, июль 2008 г., Git v1.6.0-rc0 упоминается:

Документация: Как игнорировать локальные изменения в отслеживаемых файлах

Этот патч объясняет, что .gitignore касается только неотслеживаемые файлы и направляет читателя к

git update-index --assume-unchanged

при необходимости игнорировать незафиксированные изменения в уже отслеженных файлах.

0 голосов
/ 27 апреля 2019

Легко понять *, что эти две категории - это одно и то же, если вы сделаете их немного более явными.


«совершенный» означает

просто совершено (подразумевается "... и с тех пор не было выполнено никаких других операций")


«немодифицированный» означает

без изменений с момента последнего коммита


* (в основном для ответа на заглавный вопрос, но см. Ответ Торека для уточнения деталей)

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