Возможно ли исправление подмодуля в Git из родительского проекта?

У меня есть main проекта, которая содержит подмодуль foo. Для этого конкретного проекта я хотел бы внести небольшое изменение в foo которое применимо только к этому конкретному main проекту.

main/
  + .git
  + main.c
  + lib/
  |   + bar.c
  + foo/           # My 'foo' submodule
      + .git
      + config.h   # The file I want to patch from 'main'
      + ...

Общим решением было бы перейти к моему подмодулю, сделать фиксацию Applied patch for main в новом ветки, называемом main-project, а затем нажать его. К сожалению, это очень плохой подход, потому что я вношу изменения в foo что имеет значение только для main. Кроме того, когда я обновляю foo до последней версии, мне придется чересчур-выбрать патч, который вводит много шума в историю foo.

Другое решение - иметь реальный файл исправления на main который применяется к foo непосредственно перед сборкой. К сожалению, поскольку это изменяет содержание подмодуля, и я буду иметь uncommitted измененный на foo, так что это тоже нехорошее решение.

Идеальное решение - отслеживать мой патч с помощью Git, но на верхнем уровне (например, непосредственно на main, а не на foo). Теоретически, можно было бы добавить blob в tree Git, который указывает на местоположение подмодуля:

blob   <sha> main.c
tree   <sha> lib/
commit <sha> foo
blob   <sha> foo/config.h

С этой идеей исправленный файл config.h принадлежащий foo будет отслеживаться по main.

Как это можно сделать?

Ответ 1

Я по-прежнему буду иметь второй вариант (иметь основной файл исправления на главной странице), но приспособить процесс сборки к:

  • сделать копию config.h в подмодуле
  • применить патч
  • строить
  • восстановить config.h в исходное содержимое.

Таким образом, я сохраняю статус подмодуля неизменным.

OP добавляет в комментарии:

Но ваше решение не работает в среде IDE, Intellisense будет запутано -

Истинный: для этого я автоматически применяю патч для проверки и удаляю его при проверке с помощью драйвера фильтра smudge/clean content.
Таким образом, патч остается на месте во время всего сеанса, но исчезнет с любого состояния git/diff/checkin.

Однако это не идеальный вариант, и, похоже, не существует родного Git-способа справиться с этим.

Ответ 2

Самый простой способ перенести специфичные для проекта патчи в историю вендора - это клонировать репо вендора, перенести изменения в виде ветки проекта и объявить этот клон как вышестоящий .gitmodules.

Это делает работу над вашими изменениями в исходном проекте вендора совершенно обычной, git clone --recurse-submodules yourproject работает отлично, изменения вашего подмодуля могут быть перенесены обратно в ваш восходящий поток вашего проекта (субмодуль repo origin remote), все работает.

Единственным дополнительным заполнением является обновление версии вашего субмодуля до последней версии кода поставщика, которую кто-то должен получить и объединить из (далее по течению) репо поставщика

... но это также совершенно нехарактерно: способ получить и объединить с репо вендора, сделать это. git remote add vendor u://r/l; git fetch vendor; git merge vendor/master. Или, если вы предпочитаете rebase для слияния, сделайте это. Как только вы это сделаете, отправьте результаты в свой подмодуль origin, версию своего проекта, как обычно.

Ответ 3

  Идеальным решением было бы отслеживать мой патч с помощью Git, но на верхнем уровне. (например, непосредственно на основной, а не на foo). Теоретически, было бы возможно Добавьте BLOB-объект в дерево Git, указывающий на местоположение субмодуля:

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

Альтернатива 1: патч

Есть ли более элегантный/обтекаемый способ сделать патч в Git субмодуль, кроме принятого ответа?

Если вы хотите вообще не связываться с подмодулем, я бы предложил копирование/проверка рабочего дерева в другом месте и использование только этого во время сборки. Таким образом, подмодуль всегда "чистый" (и, возможно, "неизменяемый", с точки зрения main, и вам остается только беспокоиться об этом в каталоге сборки.

Пример упрощенного процесса сборки:

cd main
mkdir -p build
cp -R foo/ build/
cp myconfig.patch build/
cd build
patch <myconfig.patch
make

Обратите внимание, что это сборка только foo, и что процесс сборки main не требуется следует изменить, кроме того, чтобы указывать на build/ вместо foo/.

Если вы не собираетесь модифицировать сам TG46/предпочли бы сохранить его "нетронутым", Вы также можете превратить его в пустой репозиторий и использовать GIT_WORK_TREE="$PWD/build" git checkout HEAD вместо cp, так что это только проверено во время сборки. Это похоже на то, как makepkg (8) делает это (по крайней мере, по моему опыту работы с AUR) по порядку чтобы избежать изменения оригинальных источников (массив $source против $srcdir). Это также разделяет извлечение источника из самой сборки (prepare() vs build()). Смотрите также PKGBUILD (5) и Создание пакетов. В вашем случае, разработка и IDE также участвуют, так что это может быть сложнее если вы хотите проверить как исходные файлы, так и файлы сборки одновременно.

Доводы:

  • Исходники отделены от файлов сборки
  • main не влияет на foo
  • Не зависит от git/делает это просто проблемой автоматизации сборки
  • Требуется только файл патча

Против:

  • Необходимо постоянно обновлять файл патча (вместо изменений в базисе)
  • Нужно изменить процесс сборки

Я бы пошел с этим, если ваши патчи маленькие и/или очень специфичные для main.

P.S.: Можно пойти еще дальше и отследить версию foo непосредственно в процессе сборки вместо использования подмодулей, если вы хотите:

Переместите foo на один каталог вверх, затем в процессе сборки:

cd build
GIT_DIR='../../foo/.git' git checkout "$myrev"
patch <myconfig.patch
make

Альтернатива 2: отдельная ветвь

Кроме того, когда я обновлю foo до последней версии, мне придется выбрать патч, который вносит много шума в историю foo.

Вы действительно не должны выбирать это, вы можете просто сохранить изменения в вместо этого свою ветку и сливайте master время от времени.

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

Перебазирование ваших коммитов на master также возможно.

Доводы:

  • Нет необходимости в отдельном хранилище
  • Держит рабочее дерево в одном месте (не нужно связываться с вашей IDE)

Против:

  • Репозиторий Pollutes foo с несвязанными коммитами (при слиянии)
  • Репозиторий Pollutes foo с несвязанными объектами фиксации (при перебазировании)
  • Мрачная история эволюции ваших изменений в config.h (при перебазировании)

Альтернатива 3: мягкая вилка

Кроме того, когда я обновлю foo до последней версии, мне придется выбрать патч, который вносит много шума в историю foo.

К сожалению, это очень плохой подход, потому что я делаю изменения в Foo это имеет значение только для

Если вы хотите изменить foo для соответствия main, но не связываться с foo вверх по течению, почему бы не создать софт-форк из foo? Если вы не слишком заботитесь о foo-fork история, вы можете просто зафиксировать свои изменения на main-project ответвление и синхронизируйте его с foo master через rebase:

Создание форка:

cd foo
git remote add foo-fork 'https://foo-fork.com'
git branch main-project master
git push -u foo-fork main-project

Синхронизация:

git checkout main-project
git pull --rebase foo/master
# (resolve the conflicts, if any)
git push foo-fork

Pros:

  • Легко синхронизировать с восходящим потоком (например, с помощью pull --rebase)
  • Держит рабочее дерево в одном месте (не нужно связываться с вашей IDE)

Против:

  • Мрачная история эволюции ваших изменений в config.h (из-за перебазирования)

Дополнительным преимуществом использования патча вместо перебазирования является то, что вы сохраняете История патча. Но если вы хотите, чтобы все было очень просто, Я полагаю, что так оно и есть.

Альтернатива 4: хард-форк

Если вы обнаружите, что foo меняется слишком сильно/слишком часто и/или вам нужно слишком много исправлять многие вещи, ваш лучший выбор, вероятно, это создание полного форка и сбор вишни их изменения вместо.

Ответ 4

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

Вы можете применить свое изменение локально как часть основной ветки master, а затем периодически обновлять, объединяя изменения из основной ветки разработки.