Git pu sh старая версия файла "--skip-worktree" - PullRequest
0 голосов
/ 04 августа 2020

Я использую Git для переноса кода с машины DEV на машину PROD. Большая часть кода является обычным, но у меня есть c файлы конфигурации, которые должны отличаться между ними.

Я хочу, чтобы мой git представлял текущее состояние машины PROD. Идея состоит в том, чтобы передать sh большинство файлов из DEV и указать только c local_config_file из PROD. Поэтому я выполнил следующую команду на DEV, чтобы DEV local_config_file не влиял или не влиял на GIT:

git update-index --skip-worktree local_config_file

Однако, когда я пытаюсь изменить код sh с DEV на git Я получаю эту ошибку:

error: failed to push some refs to [my_git]
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again. 

И когда я запускаю git diff origin/branchName branchName (все еще на DEV), он показывает мне только отличия от файлов, которые я хочу сохранить sh (это нормально ...) и отличия от старой версии local_config_file, несмотря на то, что она установлена ​​как skip-worktree.

В экспериментальных целях я попытался принудительно ввести sh принудительно в git и получить старую версию моего local_config_file в git ...

Кто-нибудь знает, как это решить? Спасибо за помощь

1 Ответ

2 голосов
/ 05 августа 2020

Я хочу, чтобы мой git представлял текущее состояние машины PROD.

ОК.

Помните, что Git сохраняет фиксирует , а не файлы. Фиксирует файлы хранилища - фактически, каждая фиксация содержит полный снимок каждого файла, но Git работает по одному фиксации за раз. Каждый репозиторий Git по своей сути представляет собой набор коммитов, и в каждом репозитории либо есть коммит, либо он отсутствует. Таким образом, фиксация выполняется по принципу «все или ничего»: все файлов, иначе у вас вообще нет фиксации.

Идея состоит в том, чтобы pu sh большинство файлов из DEV ...

Вы не можете сделать это в Git. Вы должны сделать sh коммитов .

... и указать только c local_config_file из PROD. Поэтому я выполнил следующую команду на DEV, чтобы DEV local_config_file не влиял или не подвергался влиянию GIT:

git update-index --skip-worktree local_config_file

Это не делает то, что я думаю, как вы думаете. Это не непосредственный источник следующей проблемы, но это то, что вам нужно будет устранить, прежде чем вы сможете получить то, что хотите.

error: failed to push some refs to [my_git]
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.

Это, с другой стороны, означает более или менее то, что он говорит. Однако его совет может быть немного неверным для вашей конкретной ситуации.

Что нужно знать о коммитах и ​​индексе Git

При использовании Git помните следующее:

  • Git - это все о коммитах. Ветви - а точнее ветка имена - полезны для вас (и для Git, чтобы помочь вам), но Git на самом деле относится к коммитам .

  • Каждая фиксация имеет уникальный номер. Это число выглядит случайным (но не совсем случайным) ha sh ID , а не простым простым счетным числом, но они все равно пронумерованы. Это позволяет любым двум Git общаться друг с другом и согласовывать, есть ли у них конкретный коммит или нет, просто сравнивая номера коммитов.

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

Поскольку файлы внутри фиксации заморожены, дедупликации и Git - только они должны быть извлечены из коммита, чтобы вы могли их использовать. Место, где эти файлы размораживаются и распаковываются - восстанавливаются или восстанавливаются для вашего использования - называется вашим рабочим деревом или рабочим деревом . Вы выбираете какую-то фиксацию - в конечном счете, по ее внутреннему номеру фиксации - и просите Git извлечь эту фиксацию. Git делает это, и теперь у вас есть все файлы из , которые фиксируют.

В этом смысле у вас есть две копии каждого файла: текущая фиксация заморожена и де- дублированная Git -только копия, и ваше рабочее дерево имеет копию обычного формата, с которой вы можете работать.

Вот где Git становится немного сложнее: создать новый commit, Git не использует вашу копию рабочего дерева! Эта копия ваша , а не Git. Вместо этого Git тайком сохраняет третью копию. Это не совсем копия, потому что она предварительно дедуплицирована, но действует как единая копия. Эта третья копия находится в том, что Git называет, по-разному, index , или staging area , или иногда - редко в наши дни - кеш-память .

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

Что делает skip-worktree

Бит --skip-worktree сообщает Git: Если моя копия рабочего дерева не соответствует вашей индексной копии, это нормально. Ничего не говори об этом. Не используйте копию рабочего дерева для обновления копии индекса. Просто пропустите копию рабочего дерева. Таким образом, ваш предложенный следующий коммит сохранит индексную копию, которую вы извлекли из некоторого существующего коммита.

Когда вы используете git checkout чтобы переключиться на другой существующий коммит, Git по-прежнему будет копировать файл замороженного формата в индекс (а иногда и в ваше рабочее дерево: эти детали меняются в зависимости от использования Git s sparse Касса режим). Бит --skip-worktree означает, что он не будет жаловаться, когда вы измените копию рабочего дерева, а git add не будет обновлять копию индекса.

Пришло время вернуться к этой части вашей конкретной проблемы:

Я хочу [сделать новую фиксацию], чтобы представить текущее состояние машины PROD.

Git сделает новую фиксацию из файлов, которые находятся в индексе Git, а не из файлов в вашем рабочем дереве. Итак, чтобы сделать это, вы должны загрузить свой собственный индекс Git с правильными файлами.

Идея состоит в том, чтобы [использовать] большинство [ly] файлов из DEV и только c local_config_file из PROD ...

Итак, чтобы сделать этот , вы должны убедиться, что индексная копия Git этого локального файла конфигурации совпадает с копией в PROD . Если это не так:

  1. переместите локальный файл конфигурации в сторону;
  2. сбросьте флаг --skip-worktree (git update-index --no-skip-worktree local_config_file);
  3. установить файл конфигурации PROD в качестве локального файла конфигурации;
  4. используйте git add, чтобы скопировать правильный файл в индекс Git;
  5. не стесняйтесь переключать конфигурацию обратно и / или устанавливать flags снова, теперь, когда копия индекса совпадает с файлом, который вы хотите иметь в следующем коммите.

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

Как увидеть, что на самом деле находится в индексном файле

Когда вы установили Флаг --skip-worktree установлен для индексной копии файла, трудно понять, что на самом деле в индексной копии, потому что Git не показывает вам файл напрямую. Git обычно просто показывает , соответствует ли копия индекса копии рабочего дерева . Параметр --skip-worktree сообщает Git: не беспокоиться о сравнении (индексные копии и копии рабочего дерева), а также о запрете обновлений.

Однако вы можете просматривать содержимое индекса напрямую :

$ echo 'skip this in worktree' > file
$ git add file
$ git commit -m 'add file to skip'
[master 7cee93e] add file to skip
 1 file changed, 1 insertion(+)
 create mode 100644 file
$ git update-index --skip-worktree file
$ ls
file    README
$ git cat-file -p :file
skip this in worktree
$ echo more >> file
$ git status
On branch master
nothing to commit, working tree clean
$ cat file
skip this in worktree
more
$ git add -f file
$ git cat-file -p :file
skip this in worktree
$ 

Выше показано, как, несмотря на мои изменения в файлах file, git status и git add, оставить копию index в покое. Но я могу просмотреть копию индекса с помощью git cat-file -p :file. Начальное двоеточие : здесь означает , позвольте мне увидеть копию, которая находится в индексе .

Если я сниму флаг --skip-worktree, git status начнет говорить больше:

$ git update-index --no-skip-worktree file
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file

no changes added to commit (use "git add" and/or "git commit -a")

Опять же, все, что делает --skip-worktree, - это убедиться, что git status не жалуется и git add фактически не добавляют . Индексная копия файла file все еще там, и все еще идет в новые коммиты, которые я делаю.

the tip of your current branch is behind its remote counterpart

Ваша последняя проблема здесь. Вспомните, как, как упоминалось выше, каждая фиксация состоит из двух частей:

  • данные в фиксации - это моментальный снимок всех файлов.
  • метаданные в фиксации - это информация о фиксации, включая автора и коммитера (имя и адреса электронной почты), отметки даты и времени и т. д.

В метаданных каждый commit хранит уникальный номер своего непосредственного предшественника коммита (или для фиксации слияния, предшественника, во множественном числе). То есть при некоторой фиксации Git может найти фиксацию, которая приходит перед этой фиксацией. Git вызывает этот сохраненный ha sh ID как родительский коммита.

Git не может go пересылать эту информацию во времени. Git может только go назад . Но этого достаточно: учитывая номер фиксации - ha sh ID - последней фиксации, Git может работать назад, оттуда, к более ранним фиксациям.

Это актуальные ветки в Git. имена - имена веток, такие как master и develop - всего лишь уловка: каждое имя хранит случайно выглядящий ha sh ID последнего коммита, то есть считаться частью этой ветви. Это означает, что важны сами коммиты. имена просто позволяют вам или Git начать работу.

Мы можем нарисовать эту ситуацию следующим образом:

... <-F <-G <-H

где H - ха sh ID последнего коммита в некоторой цепочке коммитов. Чтобы быстро найти H ha sh ID, Git имеет имя, например master, сохраните это ha sh ID:

...--F--G--H   <-- master

Используя это ha sh ID , Git может извлечь фиксацию H или, если вы попросите, использовать сохраненный родительский идентификатор ha sh ID H - тот, который для G - go назад во времени, одна фиксация для фиксации G и извлеките этот коммит. Найдя фиксацию G, Git может использовать свой сохраненный родительский идентификатор ha sh, который находит F, для извлечения фиксации F. Обнаружив эту фиксацию, Git может go вернуться еще дальше, если и когда необходимо.

Чтобы сделать новый коммит, который следует после фиксации H, Git:

  1. собирает все необходимые метаданные: ваше имя, ваш адрес электронной почты и так далее;
  2. получает H ха sh ID (он тут же удобен в имени master);
  3. замораживает это вместе со всеми индексными копиями всех файлов в новую фиксацию, которую мы назовем I; и, наконец, хитрым, но умным ходом
  4. записывает новый ha sh ID I в name master.

Это означает, что мы go от:

...--F--G--H   <-- master

до:

...--F--G--H   <-- master
            \
             I

до:

...--F--G--H--I   <-- master

и теперь у нас есть новый коммит на master, который просто добавляет в цепочку.

К отправьте этот новый коммит другому Git, у нас есть Git вызов их Git, полагая что их Git также имеет:

...--F--G--H   <-- master

в его набор коммитов и его имена веток. У нас есть Git, чтобы отправить им наш новый коммит I, так что теперь они имеют:

...--F--G--H   <-- master
            \
             I

в их репозитории. Затем наш Git спросит их: Если все в порядке, установите master так, чтобы он относился к фиксации I сейчас.

Когда они скажите нет и добавьте:

  ! [rejected]        master -> master (non-fast-forward)

это означает, что когда мы предположили , у них было:

...--F--G--H   <-- master

мы были просто неправильно . Вместо этого у них было что-то вроде этого:

             J   <-- master
            /
...--F--G--H

в их репозитории. Мы отправили им наш коммит I, в котором говорится, что коммит H является его родительским. Они сказали ОК - это нормально - и теперь у них есть:

             J   <-- master
            /
...--F--G--H
            \
             I

Но потом мы попросили их, если все в порядке, переместить их master, чтобы идентифицировать фиксацию I. Если бы они сделали это, они бы потеряли свою фиксацию J!

Помните, Git - любой Git - работает, начиная с имени ветки, чтобы найти последний коммит, затем работает в обратном направлении . Если мы установим их master на запоминание фиксации I, и они будут работать в обратном направлении оттуда, у них будет фиксация I с предшествующей фиксацией H, а затем оттуда в обратном порядке, как и раньше. Но коммит J не назад от H. Это вперед .

Как действовать дальше

Что нам нужно сделать, тогда, если мы не хотим, чтобы они выбросили фиксацию J после все, это получить от них свою фиксацию . Мы можем сделать так, чтобы наш Git вызвал свои Git и , получил commit J и поместил его в наш Git. Это дает нам:

             J   <-- origin/master
            /
...--F--G--H
            \
             I   <-- master

в нашем репозитории. Наш коммит I все еще существует, а наш master все еще помнит наш коммит I.

Теперь нам нужно создать новый коммит - назовем его коммит K, хотя на самом деле у него будет какой-то уродливый случайный идентификатор ha sh, который основан на их фиксации J. Мы возьмем наш снимок в нашем коммите I и изменим его на снимок, который начинается с их снимка в J, но принимает все, что мы изменили , когда мы перешли с H на * 1402. *.

Конечно, у вас есть одно дополнительное осложнение. У вас есть файл конфигурации, который, по вашему мнению, должен храниться в Git (ошибка, но вы застряли). Возможно, вам потребуется переместить ваш в сторону - возможно, полностью из вашего собственного Git репозитория - и установить производственную версию на место, и сбросить бит skip-worktree.

После того, как вы все это сделаете, вы можете сделать любую фиксацию - включая H, I и / или J - из вашего репозитория Git в ваше рабочее дерево и в Git. индекс. Ваша конфигурация полностью удалена из вашего рабочего дерева; вы работаете с производственной версией. Теперь вы можете скопировать фиксацию I в новую фиксацию K, следующую за J. Есть несколько способов сделать это, но, возможно, самый простой из них:

git checkout -b update-prod origin/master    # make a branch to use commit `J`

, что дает вам то, что мы можем нарисовать следующим образом:

             J   <-- update-prod (HEAD), origin/master
            /
...--F--G--H
            \
             I   <-- master

Теперь легко использовать git cherry-pick скопировать коммит I. Обычно я бы назвал копию I', но я использовал имя K, поэтому давайте напишем команду и нарисуем результат:

git cherry-pick master

             J   <-- origin/master
            / \
...--F--G--H   K   <-- update-prod (HEAD)
            \
             I   <-- master

Теперь вы можете переименовать свой старый master на old-master и ваш текущий update-prod на master:

git branch -m master old-master
git branch -m update-prod master

, что дает вам:

             J   <-- origin/master
            / \
...--F--G--H   K   <-- master (HEAD)
            \
             I   <-- old-master

(вам, вероятно, даже не нужно имя old-master больше, но не помешает оставить его на некоторое время.) Теперь вы можете снова установить бит skip-worktree в файле конфигурации, скопировать конфигурацию разработки на место и протестировать ее как столько, сколько необходимо.

Когда ваше тестирование в порядке, вы можете запустить:

git push origin master

На этот раз ваш Git вызывает их Git, предлагает ваш новый коммит K , а затем просит их установить их master так, чтобы они указывали на теперь совместно используемый коммит K. Их Git больше не имеет оснований отклонять запрос. Таким образом, на их Git это приведет к:

...--F--G--H--J--K   <-- master

Commit I не отображается в их Git. Он может быть там, потому что вы отправили его ранее, но никто не может найти его, потому что способ, которым мы, включая Git- find , фиксирует *1458* - это начинать с имен веток и использовать их для найдите последние коммиты, а затем работайте в обратном направлении, и нет name в их Git, по которому можно найти фиксацию I.

Как только вы удалите свое собственное имя old-master, у вас больше не будет удобного способа найти фиксацию I, и будет казаться, что его никогда не существовало. У нас не будет причин рисовать коммиты с изгибом в них, так как ваш собственный Git теперь будет иметь:

...--F--G--H--J--K   <-- master (HEAD), origin/master

ваш Git обновит ваш собственный origin/master - ваш Git память об их Git master - как только они примут ваш git push запрос.

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