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



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



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



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



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

7 ответов:

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

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

Это даст вам выбор двух разных кусков из git add --patch.

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

Вероятно, вы могли бы подойти к этому по-разному. Я мог бы подумать о двух: 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). Например, следующие три файла,

Конфиг.образец.основание :

Original: 
          some config

Конфиг.образец.последние :

Original: 
          some config

Upstream:
          new upstream 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

Вы, вероятно, могли бы написать это без особых усилий. Кстати, 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

Если вы хотите полное слияние 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" или "deleteme" или как угодно. Слияние происходит с идентификатором содержимого в записи индекса.

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

Для diff просто config.example в вашем локальном РЕПО с соответствующим файлом в upstream/HEAD Вы можете запустить:

git diff upstream/HEAD config.example

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

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

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

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

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

Comments

    Ничего не найдено.