Интерактивно объединять файлы, отслеживаемые с помощью git и не проверенных локальных файлов

Я использую пару программных пакетов (например, gitlab), которые вы устанавливаете, клонируя из своего репозитория git. Обычно они поставляются с некоторыми config.example (под контролем версий), которые вы копируете в свой собственный config файл (не под управлением версиями или даже игнорируемым в .gitignore) и адаптируетесь к вашим потребностям.

Когда обновляемый пакет обновляется и, например, изменяет параметры файла конфигурации, которые, очевидно, будут отображаться только в config.example.

Есть ли цепочка из git команд, которые мне не хватает, которые могут помочь мне сравнить изменения config.example с новым в upstream/HEAD и, возможно, даже объединить их в интерактивном режиме в мой локальный файл config?

Было бы здорово, если бы я мог получить что-то вроде режима интерактивного патча в git add/commit --interactive.

Ответ 1

git checkout --patch выбирает diff hunks, простейшим здесь может быть размещение вашего контента по восходящему пути, выполнение этого и очистка после:

cp config  config.example
git checkout -p upstream   config.example
mv config.example  config
git checkout @  config.example

вы получите выбор из двух столбцов из git add --patch.

Ответ 2

  • Вы можете использовать vim как инструмент слияния с vimdiff.
  • Emacs может сделать это также с помощью ediff-mode.

Ответ 3

Вероятно, вы могли бы подойти к этому по-разному. Я мог бы подумать о двух: git-merge-file и добром patch. Метод git-merge-file предлагает некоторую интерактивность, тогда как метод patch не имеет значения.

Решение с git-merge-file

Допустим, у вас есть исходный файл config.example, который вы используете для создания локального неверсированного файла, config.local. Теперь, когда обновления восходящего потока config.example, вы можете выполнить следующие шаги, чтобы слить любые новые изменения.

$ git fetch
$ git show master:config.example > config.example.base
$ git show origin/master:config.example > config.example.latest
$ git merge-file config.local config.example.base config.example.latest

Это обновит config.local с помощью обычных маркеров конфликтов, которые вам придется разрешить с помощью вашего любимого инструмента слияния (ediff в Emacs приятно, я уверен, что для Vim существуют аналогичные режимы). Например, следующие три файла,

config.example.base

Original: 
          some config

config.example.latest

Original: 
          some config

Upstream:
          new upstream config

config.local

Original: 
          some config

My changes:
          some other config

будет объединено следующим образом:

Original: 
          some config

<<<<<<< config.local
My changes:
          some other config
=======
Upstream:
          new upstream config
>>>>>>> config.example.latest

Вы могли бы script сделать это без особых усилий. Btw, git-merge-file может работать на любых 3 файлах, они не должны контролироваться версией под git. Это означает, что можно использовать его для слияния любых трех файлов!

Решение с patch:

Предполагая, что имена файлов, config.local и config.example, должны работать.

$ git fetch
$ git diff master..origin/master -- config.example | sed -e 's%\(^[-+]\{3\}\) .\+%\1 config.local%g' > /tmp/mypatch
$ patch < /tmp/mypatch

Ответ 4

Если вы хотите полное слияние git, вы можете получить git, чтобы сделать это с произвольным контентом, настроив индексную запись как git read-tree, а затем вызывая git обычный драйвер слияния только на эту запись. git относится к различным версиям контента как "этапы"; они 1: оригинал, 2: ваш, 3: их. Слияние сравнивает изменения от 1 до 2 и от 1 до 3 и делает свою вещь. Чтобы настроить его, используйте git update-index:

orig_example=        # fill in the commit with the config.example you based yours on
new_upstream=        # fill in the name of the upstream branch

( while read; do printf "%s %s %s\t%s\n" $REPLY; done \
| git update-index --index-info ) <<EOD
100644 $(git rev-parse $orig_example:config.example)  1 config
100644 $(git hash-object -w config)                   2 config
100644 $(git rev-parse $new_upstream:config.example)  3 config
EOD

и вы выполнили слияние пользовательского контента для этого пути. Теперь сделайте это:

git merge-index git-merge-one-file -- config

и он будет либо автоматизировать, либо оставить обычные пометки конфликтов, исправить его, как вам нравится, и git rm --cached --ignore-unmatch (или сохранить) запись индекса, если вы хотите.

Путь, который вы указали в индексе ( "config" во всех трех записях здесь), кстати, не должен уже существовать или иметь какое-либо отношение к чему-либо. Вы можете назвать его "wip" или "deleteeme" или что угодно. Слияние состоит из содержимого id'd в записи индекса.

Я думаю, что вы, вероятно, будете делать то, что хотите. Если вы действительно хотите выбрать и выбрать из вышеперечисленных изменений, вы можете поместить свое собственное содержимое в config.example и сделать git checkout -p upstream -- config.example, что делает обратное к git add -p, а затем вернет вещи так, как они были.

Ответ 5

Чтобы развернуть только config.example в вашем локальном репо с соответствующим файлом в upstream/HEAD, вы можете запустить:

git diff upstream/HEAD config.example

К сожалению, я не знаю, как сделать git напрямую применить изменения к файлу, который не отслеживает git.

Ответ 6

Существует инструмент, называемый sdiff, который может делать то, что вы хотите.

Вызовите его (в вашем случае) с помощью sdiff -o config config.example config

Ответ 7

Следующее должно работать:

git diff <some-args> | perl -pe 's/path\/to\/changes\/file/path\/other/g' > t
patch -p1 < t 
rm t