Я думаю, что ваша проблема легче и сложнее, чем вы ожидаете. Это проще, потому что Git не хранит изменений вообще. Git просто хранит коммитов . Это сложнее, потому что вы должны будете что-то вводить в (некоторые) метаданные Git, где-нибудь - или просто полностью обойти Git для этой конкретной операции. Если вы не делаете обхода Git полностью для этой конкретной операции, добавление записей в .gitignore
не будет плодотворным.
Основы закулисной работы
Позвольте мне попытатьсяуточнить, что происходит за кулисами. Система хранения Git - все о коммитах , а не файлах. Каждый коммит хранит файлы (некоторый набор) как полный снимок. Каждый коммит также хранит некоторые метаданные для каждого коммита: кто его сделал (автор и коммиттер), когда (две отметки даты и времени для автора и коммиттера) и почему (сообщение журнала). Каждый коммит уникально идентифицируется по его (уникальному) хэш-идентификатору. Каждый коммит также хранит уникальные хэш-идентификаторы для своего непосредственного родительского коммита или коммитов.
Эти хеш-идентификаторы, хранящиеся в каждом коммите и указывающие назад на некоторые более ранние коммиты,сформировать историю в репозитории Git. То есть, если вы только что сделали новый коммит в своем репозитории, в своей ветке master
, этот новый коммит имеет какой-то большой уродливый хеш-идентификатор, уникальный для него. Давайте назовем этот хэш-идентификатор H
. Внутри самого коммита H
есть строка с надписью «Мой родительский коммит ______»: заполните пустое значение хеш-идентификатора коммита, который вы извлекли как ваш master
до того, как вы сделали новый коммит, нодавайте просто для удобства вызовем этот коммит G
. Коммит G
, конечно, будет иметь еще один хэш-идентификатор родительского коммита: назовем это F
. Если каждый коммит находится в красивой аккуратной простой линии (нет веток и слияний), и мы рисуем их, мы получаем:
... <-F <-G <-H
, где H
- это последний коммитв этой строке. Он указывает на своего родителя G
, который указывает на F
, и так далее, вплоть до самого первого сделанного когда-либо коммита, который мы можем назвать A
. Поскольку это первый коммит, он не может и, следовательно, не имеет более раннего коммита: он указывает ни на что другое и называется корневым коммитом .
Для find , который является последним , Git хранит необработанный хэш-идентификатор последнего коммита под именем ветви, как master
. Таким образом, имя master
просто указывает на фиксацию H
сейчас:
...--F--G--H <-- master
Буквально минуту назад, перед тем как вы сделали коммит H
, имя master
указывало на фиксацию H
. При создании нового коммита хранится:
- новый снимок,
- с вами в качестве автора и коммиттера,
- "сейчас" как две даты-и отметки времени,
- ваше сообщение журнала в качестве сообщения журнала;и
- предыдущий branch-tip-commit в качестве его родителя.
content в новом снимке происходит из индекса. Сохранение индекса не составляет различий: это следующий моментальный снимок фиксации, готовый к выполнению.
Когда вы запускаете git status
, Git запускает git diff
внутренне, чтобы увидеть, что отличается в следующем коммите, готовом к выполнению, по сравнению с текущим коммитом. Все, что отличается здесь, указано как "изменения, которые необходимо зафиксировать", но они не изменения , это целые файлы. В то же время, Git также запускает второй git diff
, чтобы сравнить файлы индекса с файлами рабочего дерева. Что бы ни было отличается здесь указано как "изменения, не подготовленные для фиксации", но они не изменения , это целые файлы. Файлы рабочего дерева находятся только в форме, которую вы можете видеть и работать с ней. (Индексные копии находятся в специальной форме Git-only, готовой для фиксации.)
Yoу вас есть один "главный" другой Git, который вы называете origin
. С этим Git вы вызываете его - или, скорее, ваш Git вызывает его - время от времени, и ваш Git сравнивает хеш-идентификаторы с их Git. Если у вашего Git есть хеш-идентификаторы, которых у них нет, ваш Git может дать своему Git эти новые коммиты. Затем, независимо от того, дали ли вы им новые коммиты, ваш Git может попросить их, например, . Пожалуйста, установите master
, чтобы указать H
. (Чтобы это работало, вы должны сначала отправить им коммит H
.) Они либо согласны, и теперь у них есть новый коммит H
, и их master
также указывает (их копия) коммитаH
, или они говорят: «Нет, я не буду этого делать ______» (заполните бланк с указанием причины).
Как получить то, что вы хотите
Самый простой вариантэто добавить третий Git-репозиторий. Этот третий Git не имеет общих коммитов ни с вашим локальным репозиторием, ни с Git по адресу origin
: ваш Git и origin
Git продолжают делиться коммитами, которые однозначно идентифицируются по их хэш-идентификаторам и которые соединяются друг с другом черезэти обратные ссылки внутри каждого коммита. Этот третий Git ранее не встречал ваш Git и имеет различные хеш-идентификаторы в истории : ни один из них не совпадает ни с одним из идентификаторов ваших коммитов (по определению, поскольку все хеш-идентификаторы коммитов везде уникальны)если только сам коммит не был передан напрямую).
Итак, после использования git remote add
для добавления имени и URL-адреса для этого третьего Git, вы можете запустить git fetch <em>name</em>
- использовать имя, которое вы использовали вgit remote add
команда - для получения всех коммитов из их Git. Здесь я буду использовать имя third
(хотя это не очень хорошее имя). Для простоты предположим, что у вас есть всего восемь коммитов, которые мы назовем от A
до H
, и что у них всего три коммита, которые мы назовем N
, O
и P
;и что у вас есть одно имя ветви, master
, и они тоже. Ваш Git получит три своих коммита, так что теперь у вас есть:
A--B--C--D--E--F--G--H <-- master
N--O--P
Обратите внимание, что ни один из их коммитов не соединяется ни с одним из ваших, и наоборот: это не связанные истории. Теперь, когда у вас есть три коммита, ваш Git должен создать имя , чтобы помнить, что third
master
указывает на коммит P
. Ваш Git будет использовать имя third/master
(или любое другое имя, которое вы использовали), поэтому теперь у вас есть:
A--B--C--D--E--F--G--H <-- master, origin/master
N--O--P <-- third/master
Я добавил origin/master
, потому что ваш Git предположительно помнит, что origin
'master
указывает на фиксацию H
, поэтому ваш Git теперь имеет два из этих имен для удаленного слежения: origin/master
, помня H
и third/master
, помня P
.
Проблема в том, что вы хотите, чтобы (некоторые) файлы выходили из коммита P
. Если вы просто выполните:
git checkout third/master
Git переведет вас в режим «отделяемого HEAD» после:
- удаления всех ваших файлов из индекса ирабочее дерево и
- копирование всех файлов из коммита
P
в индекс и рабочее дерево
, что не оставляет вас нигде из ваших файлов, где вы можете работать с ними,Затем вы можете:
git checkout master
, чтобы ваш Git удалил из индекса все файлы P
, удалил их все из рабочего дерева и повторно заполнил индекс и ваше рабочее дерево из *Файлы 1170 *.
Это не сильно помогает. Конечно, все возвращается к тому, как вам нужно работать с вашими вещами, но вы не можете видеть их файлы! Они вернулись, чтобы быть скрытыми внутри коммита P
. Хотя есть несколько вещей, которые вы можете сделать здесь, и самый прямой из них:
git checkout third/master -- <paths>
whПеред тем как аргументы <paths>
сообщают Git, какие файлы следует извлечь из коммита P
. Эта форма git checkout
не меняет ваш текущий коммит , поэтому вы все равно получите master
и, следовательно, коммит H
, извлечены. Вместо этого он извлекает именованные файлы, включая целые каталоги, полные файлов, если <paths>
называет какой-либо каталог или набор каталогов: все файлы в этом поддереве будут извлечены - с содержимым этих файлов. в и ваш индекс и ваше рабочее дерево.
Итак, если все, что вы хотите от commit P
, это, скажем, foo/
, просто используйте:
git rm -rf foo/
git checkout third/master -- foo/
и вы готовы зафиксировать обновленные файлы: все в foo/
только что вышло из фиксации P
. Шаг git rm -rf foo/
говорит Git удалить все ваши файлы, которые вам нужны, только если в вашем foo/
есть файлы, которые не находятся в коммите P
'* foo/
(ивы хотите, чтобы эти файлы пропали).
Если вам нужно быть более избирательным - если foo/
недостаточно, вы можете:
- использовать
git worktree add
чтобы создать новое рабочее дерево, содержащее коммит P
или - , вообще не использовать этот удаленный метод
third
: просто клонировать их хранилище в каком-то другом месте и выборочно копировать файлы из этого клона в ваш клонзатем git add
скопированные файлы.
Обратите внимание, что если вы используете отдельный (четвертый!) клон, становится невозможным случайно передать цепочку коммитов N-O-P
из вашего Git в ваш Git. origin
, поскольку они никогда не существуют нигде в вашем Git. Если вы добавите пульт с именем third
, эти дополнительные коммиты все еще не перейдут к origin
, если вы не выполните одно из следующих двух действий: 1
- заставить некоторые из ваших собственных веток использовать эти коммиты (напрямую, путем указания на них или косвенно, путем слияния несвязанной истории в вашу собственную историю коммитов), или
- установить один или несколько вашихсобственные локальные имена ветвей, указывающие на их коммиты
и , затем git push
эти локальные имена ветвей origin
. То есть вы должны сначала поместить N-O-P
(или любые другие сторонние коммиты Git-репо) в свою собственную историю, а затем попросить Git отправить свою историю - ваши коммиты - origin
ипопросите их Git запомнить один из этих новых коммитов под некоторым именем (обычно с именем ветки, но достаточно любого имени - ветви, тега и т. д.).
1 Вы можететакже намеренно отправьте всю цепочку N-O-P
, выполнив:
git push origin <hash-of-P>:<name>
, где <name>
- это имя, которое вы хотите, чтобы их (origin
) Git установили после получения цепочки N-O-P
. Но, вероятно, вы этого не сделаете ...