Файл находится в .gitignore
как локально, так и в репозитории ...
Как вы видели, это бесполезно, если файл фактически зафиксирован.Это потому, что .gitignore
не означает игнорировать этот файл , это действительно означает заткнись об этом файле , если он не отслежен .Если он не неотслеживаемый (если он отслеживается), перечисление файла не имеет никакого эффекта .
Я пробовал git update-index --assume-unchanged
и git update-index --skip-worktree
, но безрезультатно.
Для подтверждения было бы полезно, если бы вы точно указали, где это идет не так.На данный момент, я предполагаю, что в этом случае эти команды работают - они вообще не жалуются - но позже git fetch && git merge
жалуется, что перезапишет содержимое файла.(Возможно, вы пишете это git pull
, но если это так, я рекомендую разбить его на две составляющие команды Git, пока вы действительно не поймете, что каждая команда делает самостоятельно.)
Здесь все становится сложнее.Мы должны понимать модель Git для коммитов и слияний.С этим приходит роль index (он же промежуточная область aka cache ) и роль рабочего дерева во времяa git merge
.
Как работает слияние
Сначала давайте кратко рассмотрим коммиты и процесс слияния.Вы уже знаете 1 , что каждый коммит имеет полный снимок всех ваших зафиксированных файлов, и каждый коммит содержит хеш-код (ы) своих родительских коммитов.Следовательно, если бы мы нарисовали график коммитов после git fetch
, но до git merge
, мы могли бы увидеть это:
G--H--I <-- master (HEAD)
/
...--F
\
J--K--L <-- origin/master
1 Если вы все это уже не знает, прочитайте еще немного о Git, например, глава о ветвлении в Git Book .
Что происходит с git merge
в этом случае нужно найти коммит shared , общую отправную точку как для вашего коммита I
, на который указывает ваше имя master
, так и для их коммита L
, на который ваш коммитorigin/master
баллов.Здесь это коммит F
.
Далее Git будет сравнивать содержимое, сохраненное в коммите F
, с содержимым, сохраненным в вашем последнем коммите, I
.Это говорит Git , что вы изменили .Сравнение такое же, как и при просмотре:
git diff --find-renames <hash of F> <hash of I> # what we changed
Git также сравнивает содержимое, сохраненное в коммите F
, с содержимым, сохраненным в их последнем коммите L
.Это говорит Git, что они изменились:
git diff --find-renames <hash of F> <hash of L> # what they changed
Теперь вы можете увидеть, как на самом деле работает git merge
: он объединяет то, что вы изменили с , что ониизменено .Используя объединенные изменения, Git извлекает содержимое, сохраненное с помощью базового коммита - commit F
, и применяет объединенные изменения ко всем файлам, измененным в двух наборах изменений.Результатом, если все идет хорошо, является снимок, который должен быть зафиксирован как слияние;и Git сделает это, зафиксировав слияние и настроив вашу текущую ветку:
G--H--I
/ \
...--F M <-- master (HEAD)
\ /
J--K--L <-- origin/master
Индекс и рабочее дерево
Существует фундаментальная проблема с файлами, замороженными в коммиты:они (а) заморожены, и (б) в специальной, сжатой форме Git-only. замороженная часть отлично подходит для управления исходным кодом: эти файлы никогда не изменятся, поэтому вы всегда можете вернуться к своей предыдущей работе, проверив старый коммит.Специальная сжатая форма, предназначенная только для Git, полезна для контроля над пространством хранения: поскольку Git сохраняет каждую версию каждого файла за все время, если они не были специальными и сжатыми, у вас могло бы быть достаточно дискового пространствабыстро.Но это создает проблему: Как вы получаете на замороженный файл?Как вы можете это изменить?
Git ответит на это work-tree .Выполнение git checkout
для некоторого коммита расширяет и оттаивает файлы, которые сохраняются в этом коммите.Оттаявшие, восстановленные файлы попадают в ваше рабочее дерево, где вы можете работать с ними и изменять их.
Вдругие системы контроля версий, вот и конец истории: у вас есть замороженные файлы, которые вы не можете изменить, и ваше незамерзающее рабочее дерево, которое вы можете.Но Git добавляет эту промежуточную форму, которую Git вызывает индекс, или промежуточную область, или кэш, в зависимости от того, кто / какая часть Git выполняет этот вызов.
Понимание индекса крайне важно для использования Git,все же это редко объясняется очень хорошо.Люди (и интегрированные среды разработки) пытаются скрыть это от вас.Это не работает, и причина, по которой он не работает, важна, особенно в вашем случае.
Лучшее описание индекса, которое я знаю, это то, что войдет в ваш следующий коммит..Когда Git извлекает замороженные файлы, вместо того, чтобы размораживать и распаковывать их прямо в ваше рабочее дерево, он сначала просто размораживает их (или, точнее, собирает их в единый объединенный список, который незамороженный - в отличие от более структурированных, замороженных списков внутри коммитов).Эти теперь незамерзающие копии попадают в указатель.Все они по-прежнему Git-ified, сжаты и занимают минимальное количество памяти.
Как только файл разморозится в индексе, только тогда Git распакует его в формат рабочего дерева.Так что сначала он размораживается (индексная копия), а , а затем извлекается в рабочее дерево.Это означает, что в индексе есть копия, готовая для замораживания в next commit.
Если вы измените файл в рабочем дереве, вы должны запустить git add
для этого файла в скопировать (и сжать и Git-ify) файл, чтобы он вписался в индекс.Теперь индексная копия соответствует копии рабочего дерева, за исключением того, что индексная копия находится в специальной форме Git-only. Теперь готов перейти к следующему коммиту.
Вот как работает git status
: для каждого файла он сравнивает копию рабочего дерева с индексной копией, и если они отличается , он говорит, что файл не подготовлен для фиксации .Он также сравнивает индексную копию (в специальном формате Git-only) с HEAD
коммит-копией, и, если это отличается , он говорит, что файл подготовлен для фиксации .Таким образом, если в рабочем дереве 10 000 файлов, а индекс и фиксация HEAD
, то на самом деле их общее количество составляет 30000 (10 000 x 3 копии).Но если только две из них отличаются с точки зрения этих трех копий, только два файла будут перечислены в git status
(а копии с Git относительно малы).
Пока вы не запустите git commit
, файлы, которые отличаются в индексе, просто отличаются в индексе.Когда вы делаете запускаете git commit
, Git замораживает индекс - даже не глядя на дерево работы! - и делает это вашим новым HEAD
коммитом.Ваш новый коммит теперь соответствует индексу, поэтому теперь все индексные копии файлов соответствуют их копиям коммита `HEAD.
(Кроме того: во время конфликтующего слияния индекс играет расширенную рольВместо просто одной копии каждого файла теперь он содержит до трех копий каждого файла. Но мы не рассматриваем конфликтующие слияния здесь, поэтому у нас нетбеспокоиться об этом.)
Предполагаемые неизмененные и пропускаемые биты дерева
Теперь мы можем видеть, что делают эти два бита.Когда вы запускаете git status
, Git обычно сравнивает копию рабочего дерева каждого файла с индексной копией того же файла.Если это не так, Git говорит, что у вас есть изменение, которое не подготовлено для коммита .(Если файл вообще отсутствует в индексе, Git говорит, что файл не отслежен и , тогда файл .gitignore
имеет значение. Но если файл уже находится виндекс, файл отслеживается, и файл .gitignore
не имеет значения.)
Если вы установите биты предположения без изменений или пропуска рабочего дерева, git status
не будет сравните версию файла рабочего дерева с индексной версией.Они могут быть разными, как вам нравится, и git status
ничего не скажет о них.
Примечание tшляпа git commit
полностью игнорирует эти биты!Это просто замораживает индексную копию.Если индексная копия совпадает с копией рабочего дерева, это означает, что вы сохранили файл таким же при повторной фиксации.Ваш новый коммит имеет ту же замороженную копию, что и ваш предыдущий коммит.Ваша индексная копия продолжает соответствовать вашей HEAD
коммитной копии.
Проблема возникает, когда Git необходимо изменить файл.Допустим, вы установили бит skip-worktree (это, как правило, тот, который вы должны установить, так как другой предназначен для другой задачи, хотя на практике любой из них работает).Вы также изменили копию рабочего дерева.Запуск git status
не будет жаловаться, потому что git status
фактически не будет сравнивать копию рабочего дерева с индексной копией.
Но теперь вы запускаете git merge
,и объединение хочет принять изменения в файл.Например, Git сравнивает commit F
с коммитами I
и L
и обнаруживает, что, хотя вы не зафиксировали новую версию файла в I
, они did зафиксировать новую версию файла в L
.Таким образом, Git примет их изменения, заставит их войти в новый коммит слияния M
, а затем ... извлечет M
в ваше рабочее дерево, засоряя вашу копию файла.
Заглушая ваш файлэто плохо, поэтому Git этого не делает.Вместо этого происходит сбой при слиянии.
Что вы должны сделать с этим?
В конечном счете, вам необходимо сохранить где-нибудь свою версию файла.Это может быть внутри Git - например, в виде коммита - или вне Git, скопировав файл за пределы хранилища.Затем вы можете либо объединить свои изменения с их изменениями, либо заново выполнить их после того, как просто взяли их версию.
Это то, что на самом деле делает git stash
.Это делает коммит - ну, на самом деле два коммиты.Особенность этих коммитов в том, что они не находятся на любой ветви.Сделав коммиты, git stash
запускает git reset --hard
, чтобы выбросить ваши изменения в файл из индекса и рабочего дерева.У вас не было изменений индекса, и даже если вы сделали копию индекса, она сохраняется в коммитах stash, поэтому часть сброса --mixed
безопасна, а ваша копия рабочего дерева сохраняется в коммитах stash, поэтому --hard
часть сброса безопасна.Теперь, когда ваш индекс и рабочее дерево очищены, вы можете безопасно объединить.Затем git stash pop
- что на самом деле git stash apply && git stash drop
- может объединить вашу сохраненную версию файла рабочего дерева с текущей версией файла, используя менее безопасное внутреннее объединение, которое работает только с копией рабочего дерева.Шаг drop
сбрасывает коммиты тайника, так что они становятся не связанными 2 и в конечном итоге будут полностью удалены.
Существует несколько альтернатив использованию git stash
здесь, но никто не так прост, и никто не красив.Вы также можете использовать git stash
.
Наконец, вы можете прекратить фиксацию файла полностью.Как только файл больше не находится в индексе, он становится неотслеживаемым и не участвует ни в каких будущих фиксациях.На мой взгляд, это в конечном итоге лучшее решение, но у него есть один действительно огромный недостаток: файл содержит , зафиксированный в прошлом.Это означает, что если вы когда-нибудь извлечете старый коммит (в котором есть файл), файл будет одновременно в текущем коммите - в том старом коммите, который вы только что извлекли - и индекс, и будет отслеживаться.Когда вы переключаетесь со старого коммита на новый коммит, у которого нет файла, , то удалит файл!Вот что вы имеете в виду, когда говорите:
Я думал о том, чтобы сделать git rm --cached
, но из того, что я прочитал, кажется, что это удалит файл из репозитория ...
УдельныйОн удаляет файл из index , оставляя копию рабочего дерева в покое.Это не удаляет файл из репо , но само репо в основном состоит из коммитов , а "удалить из репо" - это бессмысленная фраза .Вы буквально не можете удалить файл из существующих коммитов: они навсегда заморожены.Вы можете только избежать помещения файла в future commits.
Это работает, но оставляет ту ловушку, которую я обрисовал выше: возврат к исторической фиксации восстанавливает файл в индексе (потому что git checkout <em>commit</em>
означает заполнить индекс из коммита и использовать его для заполнения рабочего дерева ).Как только в индексе, он будет в будущих коммитах.Для переключения на коммит, в котором отсутствует файл требует удаления из индекса, что подразумевает удаление его из рабочего дерева, и теперь копия рабочего дерева исчезла.
Итак, если вы хотите пойти по этому пути, который является хорошим способом, вы должны:
- вообще прекратить использование этого файла: переименуйте его в
config.sample
- переключиться на новое (другое) имя файла для фактической конфигурации и полностью исключить этот файл из хранилища (например, сохранить его в
$HOME/.fooconfig
)
и сделатьчто все части одного коммита, после чего старый файл конфигурации никогда не используется снова.Скажите людям, чтобы они перенесли свою конфигурацию в новое место, прежде чем переходить на новую версию программы foo
.Сделайте это ударом основной версии, потому что поведение отличается.
2 См. Думайте как (а) Git .