TL; DR
Вы не можете получить то, что вы хотите. Не делай так. Вместо этого рассмотрите возможность сохранения одного точечного файла, такого как .gitignore
, который перечисляет *
, если это нормально. Если нет, рассмотрите возможность использования программы , которая не Git, при необходимости автоматически создайте пустую папку.
Long
Существует ряд неверных предположений, которые у вас есть началось с:
- Вы думаете, что добавили папку, но Git коммит хранит только файлы , а не папки. Мы посмотрим, что это значит, и почему Git вот так, через мгновение.
- Вы думаете, что нажали папку и / или файлы. Git не pu sh файлы. Git нажимает фиксирует .
- Вы думаете, что перечисление чего-либо в
.gitignore
заставляет Git игнорировать это. Это не так.
Все это привело к проблеме, с которой вы столкнулись.
Вам необходимо начать с этого: Git базовая c единица хранилище - в любом случае, с которым вы будете взаимодействовать - это commit . Git - это всего лишь коммитов , поэтому вам нужно подробно узнать, что такое коммит и что делает.
коммитов
A Git коммит состоит из двух частей: данных и метаданных:
Данные в каждом коммите состоят из полного снимка файлов. Здесь нет папок, есть только файлы, имена которых могут содержать встроенные косые черты, такие как path/to/file.ext
.
Метаданные в каждом коммите содержат такие вещи, как ваше имя и адрес электронной почты, отметка даты и времени, когда вы сделали коммит, и ваше сообщение в журнале, объясняющее почему вы сделали коммит. В эти метаданные, предназначенные для , которые вы используете, а не для Git, включены некоторые метаданные, которые на самом деле равны для Git: список родительского коммита га sh ID . Обычно в этом списке только один коммит-идентификатор ha sh.
Каждый коммит имеет уникальный идентификатор ha sh. Этот идентификатор ha sh фактически является истинным именем коммита. Вот как Git получает данные - ваши файлы - и метаданные. Поэтому вам нужно использовать хеши коммитов, чтобы Git смог найти коммитов. Но здесь есть проблема: commit ha sh Идентификаторы большие и некрасивые и невозможны для людей.
Имена ветвей
Решение этой проблемы очевидно: у нас есть computer; у нас может быть компьютер, чтобы хранить для нас идентификаторы ha sh, используя простое имя, такое как master
или develop
, с которым могут справиться люди, чтобы запомнить правильное ха sh Я БЫ. Вот что такое имена веток: имя ветки содержит идентификатор ha sh одного - и only one - commit. Коммит Git, который мы помним в имени ветки, - это новейший или последний коммит в ветви по определению. Git называет это коммитом чаевых .
Если мы скажем git checkout master
, Git будет проверять коммит чаевых master
, используя сохраненный идентификатор ha sh во имя master
. Если мы добавим новый коммит к ветви, Git запишет новый коммит с его (одиночным) родительским га sh идентификатором к старой ветке. Новый коммит получает новый уникальный идентификатор ha sh, который Git записывает в имя ветви, и теперь коммит new является подсказкой ветви.
Предыдущий коммит все еще существует, и Git может найти его самостоятельно: Git использует имя ветви для поиска идентификатора ha sh last commit, затем читает этот коммит и использует его метаданные для поиска ха sh ID родителя. Git затем использует этот идентификатор ha sh для чтения родительского коммита. При необходимости Git использует , чтобы сохранил родительский коммит go назад еще на один шаг.
Нарисуйте свои коммиты и ветви
Визуально, если мы нарисуем это, мы получаем:
... <-F <-G <-H <--branch
, где branch
- это имя филиала, в котором хранится некоторый идентификатор ha sh, который мы здесь просто назовем H
. Это позволяет Git до найти H
. H
сам хранит в качестве родительского идентификатора ha sh идентификатор коммита, который мы просто назовем G
. Так H
указывает на G
, что позволяет Git извлекать G
из его базы данных. G
в свою очередь указывает на более раннюю фиксацию F
и так далее. Весь процесс работает, потому что имя ветви указывает на последний коммит, а оттуда Git может работать в обратном направлении.
Ваше рабочее дерево
Ничто в любом существующем коммите не может быть изменено! Однажды сделанный, каждый коммит замораживается на все время. Это включает в себя все файлы, хранящиеся внутри этого коммита. Поскольку коммиты заморожены - только для чтения - они очень хорошо работают для архивирования. Но файлы внутри фиксации хранятся в специальном, замороженном, сжатом, доступном только для чтения и Git -только формате, который может прочитать только Git. Эти файлы буквально не могут быть изменены, точно так же, как ни один Git коммит никогда не может быть изменен. Это делает эти файлы совершенно бесполезными для выполнения любой новой работы.
Решением этой проблемы является Git извлечение замороженных зафиксированных файлов из передайте в рабочую область, где файлы превращаются обратно в обычные файлы, которые ваш компьютер может использовать. Вот что git checkout
- или в Git 2.23 и более поздних версиях, git switch
- делает это: вы говорите ему, какую ветвь вы хотите использовать, Git использует ветвь name , чтобы найти правильный ha sh ID и Git извлекает все замороженные файлы из этого коммита в ваше рабочее дерево или рабочее дерево , где вы можете их видеть и работать с ними. 1
1 Здесь много подробностей о git checkout
, поэтому это просто обзор, а не точное определение .
Индекс
Итак, как вы можете видеть из этого рисунка, на самом деле существует два набора файлов, которые активны, когда вы работа с коммитом: зафиксированные копии, которые заморожены навсегда (и вообще не видны), и рабочие копии, которые вы можете видеть и работать с ними. Некоторые системы контроля версий останавливаются здесь, имея только две копии каждого файла. Git, однако, не имеет.
Git имеет то, что он вызывает, по-разному, index или область подготовки , или иногда (редко это дней) кеш . Причина, по которой у него два (или три) имени, вероятно, является комбинацией вещей: сам индекс сложен, но способ его использования в большинстве случаев относительно прост. Термин область подготовки относится к тому, как вы его используете. Мне нравится называть его индексом из-за того, что он выполняет расширенную роль во время слияний, и вам, в конечном счете, нужно будет знать об этом.
В любом случае, хороший способ подумать об индексе чтобы сделать вид, что он содержит третью копию каждого файла. 2 Эта дополнительная индексная копия находится в замороженном формате , но на самом деле не заморожена: вы может перезаписать его новой копией. Копия каждого файла в индексе - это копия, которую git commit
будет использовать при создании нового коммита. Это означает, что вы можете просматривать индекс как файлы, которые будут go в следующий коммит , или, что короче, индекс будет ваш следующий предложенный коммит .
Таким образом, git checkout
или git switch
фактически копирует файлы из выбранной вами фиксации в и индекс и вашего рабочего дерева. Индексная копия теперь готова для нового коммита, а копия рабочего дерева - это обычный файл, который хранится в папке, потому что ваша ОС требует этого. Git * копий файла, в коммитах и в индексе, вообще не сохраняются в папке. Они находятся в специальном, только для чтения, Git -только формате. 3
Что делает git add
- и именно поэтому вы должны продолжать использовать его после изменения файл в вашем рабочем дереве - это скопировать копию файла work-tree в индекс. То есть он берет ваш обновленный файл рабочего дерева, повторно сжимает его и преобразует во внутренний замороженный формат Git, а заменяет старую индексную копию новой копией. Если вы git add
файл, которого вообще не было в индексе, это добавляет новый файл в индекс, но если вы git add
файл, который был в индексе , который просто заменяет замороженную копию.
Индекс не может содержать имя папки . Файлы в индексе просто имеют длинные имена, такие как path/to/file.ext
. Поэтому, когда Git строит коммит из индекса, в новом коммите будут только файлы. 4 Это то, что мешает вам хранить папку.
Когда Git переходит к извлекать коммит, если файл в этом коммите имеет имя path/to/file.ext
и у вашего рабочего дерева нет папки path
или в папке path
отсутствует Папка to
, Git создаст , создаст path
и path/to
при необходимости. Поэтому тот факт, что Git не хранил path
и path/to
, во-первых, не важен, за исключением одного способа: невозможно сохранить пустую папку (пустой каталог) в коммите Git. 5
2 С технической точки зрения индекс содержит не копию файла, а копию режима файла , имя и blob ha sh ID . Однако, если вы не попадаете во внутреннюю работу индекса с git ls-files --stage
и git update-index
, достаточно будет считать, что индекс содержит копию.
3 Технически, опять же, на самом деле это объекты BLOB-объектов , которые имеют идентификаторы ha sh, как и коммиты. Идентификатор ha sh объекта BLOB-объекта зависит от содержимого файла, и файлы могут быть общими для нескольких разных коммитов, если содержимое совпадает, потому что каждый коммит на самом деле просто хранит идентификатор BLOB-объекта sh. Все это скрыто под другим слоем косвенности: фиксирует идентификаторы store tree ha sh, а объекты дерева хранят name-and-mode-and-ha sh ID. Но вам не нужно ничего знать об этом, чтобы использовать Git. Вы do должны знать о идентификаторах коммитов га sh и индексе.
4 Технически индекс может хранить:
- обычный файл (режим
100644
или 100755
), или - символьная c ссылка (режим
120000
), либо - * gitlink (mode
160000
).
Все три из них связаны с идентификатором ha sh: BLOB-объектом ha sh для файла или символической ссылки или фиксацией подмодуля ha sh Идентификатор gitlink.
5 Существуют некоторые приемы, ни один из которых не является полностью удовлетворительным: см. Как добавить пустой каталог в репозиторий Git? Единственное, что действительно работает, это трюк с пустым подмодулем .
Отслеживаемые и неотслеживаемые файлы
Когда вы извлекаете коммит, вы получаете три копии каждого из его файлов. Одним из них является замороженная копия в текущем или HEAD
коммите. Второй - это замороженная, но заменяемая копия в индексе. Третья - единственная копия, которую вы можете использовать самостоятельно в своем рабочем дереве. Вы можете видеть и прикасаться к копиям рабочего дерева, поскольку они являются настоящими файлами, в реальных папках на вашем компьютере, вместо внутренних Git сущностей, сохраненных каким-либо специальным Git способом.
Поскольку ваши Рабочее дерево ваше , однако, это означает, что вы можете создавать и удалять файлы и папки без участия Git. Git не будет использовать любые из этих файлов, пока вы не скопируете их в индекс Git. С git checkout
(или git switch
и / или git restore
) вы можете подать с Git на перезаписать ваши файлы рабочего дерева с копиями, которые есть у Git, но это индексные копии, которые go войдет в следующий коммит.
Так что же произойдет, если вы создадите файл рабочего дерева и не добавите его в индекс? Ответ: Git называет это неотслеживаемым файлом .
Файл без отслеживания - это, очень просто, файл, который сейчас находится в вашем рабочем дереве, но в данный момент отсутствует в индексе Git. прямо сейчас часть этого имеет решающее значение, потому что вы можете не только копировать новые файлы в индекс Git - с git add
, конечно, - вы также можете сказать Git удалить файлов из его индекса, с помощью git rm
.
Запуск:
git rm path/to/file
говорит Git: удалить копию этого файла из вашего индекса и мое дерево работы . Теперь, когда он ушел от обоих, он не будет быть в следующем коммите. Выполнение:
git rm --cached path/to/file
говорит Git: удалить копию этого файла из индекса, но не трогать копию рабочего дерева. Теперь, когда она ушла из индекса , не будет в следующем коммите.
Итак, если вы хотите, чтобы какой-то файл не был в коммите, вы должны удалить его из индекса , Это нормально, насколько это возможно. Если вы удалите его из индекса или вообще не поместили его в индекс, и файл сейчас существует в вашем рабочем дереве, этот файл является неотслеживаемым файлом .
Неотслеживаемые файлы могут раздражать, как мы увидим, но также могут быть очень важны.
git status
Когда вы запускаете git status
, Git запускает два набора сравнений. Первое сравнение сравнивает фиксацию HEAD
с индексом. Для каждого файла, который тот же , Git ничего не говорит вообще. Для файлов, которые отличаются - или являются новыми или были удалены - Git говорит, что этот файл подготовлен для фиксации . Обратите внимание, что вы не можете изменить содержимое HEAD
commit, 6 , поэтому любое изменение здесь, по определению, является чем-то, что вы изменили в индексе.
Запустив это первое сравнение, HEAD
-vs-index, Git теперь выполняет секунду сравнения. Он сравнивает все файлы в индексе с файлами в вашем рабочем дереве. Для каждого файла один и тот же , Git вообще ничего не говорит. Для файлов, которые отличаются или были удалены, Git говорит, что этот файл не подготовлен для фиксации .
Обратите внимание, что мы еще не упомянули файлы, которые являются новыми. Для каждого файла, который находится в вашем рабочем дереве, но не в вашем индексе ... ну, это именно ваши неотслеживаемые файлы . git status
делает с ними жаловаться , перечисляя их как "неотслеживаемые".
6 Хотя вы не можете изменить содержимое любой коммит, вы можете - с помощью git checkout
- выбрать другой коммит, который будет HEAD
коммит. Или, конечно, вы можете запустить git commit
и сделать новый коммит, и теперь этот новый коммит равен HEAD
коммит.
Заставить git status
заткнуться
Примерно половина цели .gitignore
файла - заставить git status
перестать жаловаться на неотслеживаемый файл. При указании имени файла или шаблона в файле gitignore
git status
not жалуется, что файл не отслежен.
Это не приводит к извлечению файлов out из индекса. Если файл уже находится в индексе Git, перечисление этого файла в .gitignore
не имеет никакого эффекта. Файл имеет значение в индексе, так что копия этого файла будет в следующем коммите ... если, конечно, вы не удалите его самостоятельно с git rm
.
Избегайте автоматического добавления файлов, которые вы не хотите фиксировать
Остальная цель файла .gitignore
состоит в том, чтобы позволить вам использовать массовые git add
операции, без также копирования некоторых файлов, которые в настоящее время не отслеживаются в индексе Git , Например, перечисление *.o
или *.pyc
означает, что теперь вы можете git add .
или git add *
. Git будет пропускать неперехваченных и игнорируемых файлов при копировании других обновленных или новых файлов рабочего дерева в индекс Git.
Без отслеживания и игнорирования поэтому полезно двумя способами
Поскольку эти файлы не не копируются в индекс, они не будут присутствовать в следующем коммите. Запуск git status
с этими файлами без отслеживания и игнорируется, вы вообще не увидите их упоминаний: их нет в индексе, поэтому они не отслеживаются, но git status
не будет жаловаться. Запуск git add .
с этими файлами, которые не отслеживаются и игнорируются, git add
не скопирует их в индекс.
Но у вас уже есть файлы в некоторых существующих коммитах
В вашем случае, Есть коммиты, в которых есть файлы внутри app/http/
. Когда вы запускаете:
git checkout <commit-specifier-or-branch-name>
и коммит, выбранный этим действием, является коммитом, который содержит файл, такой как app/http/foo.html
, Git будет:
- create
app/http/
при необходимости - записать
foo.html
в эту папку - скопировать замороженную копию
app/http/foo.html
в ее индекс
и вы будете готовы к работать с / с этим файлом. Но теперь файл является в индексе Git, поэтому он отслеживается , и git status
сообщит вам об этом, и любой массовый git add
скопирует работу -дерево файла в индекс, так что обновленный foo.html
будет в следующем коммите.
Даже если вы не копируете foo.html
в индекс Git так, чтобы индексная копия оставалась старой копией, эта старая копия будет в новых сделанных вами коммитах. Git делает коммиты из индекса, а индекс имеет старую копию.
Очевидное решение - полностью удалить копию индекса, а затем сделать новый коммит. Теперь, когда индексная копия отсутствует, в новом коммите нет app/http/foo.html
. Если app/http/
находится в .gitignore
, Git не будет, с этого момента в новой работе добавьте foo.html
либо.
Но если вы сделаете это для всех app/http/
файлов, затем, в будущем, клонируя этот репозиторий и извлекая этот коммит в новое, иначе пустое рабочее дерево, вы не получите папку с именем app/http
, поскольку не будет файлов, чье имя начинается с app/http/
, чтобы Git заметил, что ему нужно создать папку app
, а затем папку http
в папке app
.
Кроме того, предположим, что теперь у вас есть состояние, в котором вы зафиксировали a123456...
(какой-то га sh ID в любом случае), что имеет с app/http/foo.html
в нем и совершило b6789ab...
, не имеет app/http/foo.html
в нем. Если вы:
git checkout a123456
Git извлечут все файлы из этого коммита, включая app/http/foo.html
, в ваше рабочее дерево и в индекс Git ... и затем, если вы :
git checkout b789abc
Git извлечет файлы из , которые коммит, обратите внимание, что app/http/foo.html
необходимо удалить , поскольку это было в предыдущем коммите - и, следовательно, находится в индексе сейчас, а не в коммите, который вы сейчас выбираете, и, следовательно, должен быть удален из индекса. Git удалит app/http/foo.html
из индекса и из вашего рабочего дерева .
Листинг app/http/
на верхнем уровне .gitignore
или *
в app/http/.gitignore
, не только говорит Git, что он не должен автоматически добавлять эти файлы в индекс, он также дает Git разрешение уничтожать эти файлы в рабочем дереве в некоторых случаях, включая этот конкретный. Поэтому тот факт, что у вас есть эти файлы в некоторых существующих коммитах, делает вещи опасными, если вы выберете .gitignore
их. Убедившись, что все новые коммиты не имеют и игнорируют эти файлы, будьте осторожны при проверке исторических c коммитов!