Слияние ветки вверх по течению в форк с переписанной историей - PullRequest
0 голосов
/ 31 августа 2018

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

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

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

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

Что я хотел сделать, так это создать ветвь и переписать историю (например, используя BFG Repo-Cleaner ), чтобы очистить все удаленные проекты / объекты.

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

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

1 Ответ

0 голосов
/ 31 августа 2018

Обновление - После прочтения комментариев и рассмотрения моего ответа, есть некоторые вещи, которые можно уточнить, некоторые твики, которые облегчат правильное использование, и одна или две явные ошибки. Извини за это; Исходя из документации, исходным ответом было качество "черновик". Сначала я отвечу на пару вопросов, но я также рекомендую ознакомиться с отредактированным ответом ниже.

Upstream Configuration - Отношения между филиалами в каждом репо являются ключом к тому, что здесь происходит. Выборочные refspecs будут управлять этим, и пока они установлены правильно, никакая другая «восходящая» конфигурация не требуется.

Тем не менее, самое большое изменение, которое я сделаю ниже, - это переместить очищенные ветви в хранилище мостов в свое собственное пространство имен clean/*, чтобы выбор правильных ссылок в чистый репозиторий составлял много проще.

BFG удаляет исходные ветви - Это правильно, но затем после настройки origin извлечения refspec для репозитория моста последующее fetch will recreate the original branches under the prod / * `пространство имен.

Что касается вашего последнего комментария - Я думаю, что ваши предыдущие попытки просто становятся жертвами ошибок, которые возникают из-за проблем с "черновиком" в исходном ответе. Получение правильного результата абсолютно возможно, и я полагаю, что как человек, который полностью знаком с инструментами и техниками, я автоматически просматриваю исправления «на лету», которые заставили бы его работать. Но, надеюсь, это переписывание, по крайней мере, приблизит вас к тому, что вы пытаетесь сделать ...


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

Кроме того, это проще всего, если все изменения переходят из одной ветви в производственном репо в чистое репо. (Неважно, используете ли вы ветки в производственном репо, но в идеале вы бы хотели, чтобы все они были объединены в одну ветку, которая станет источником изменений для одной ветки в чистом репо.) Если нет, могут применяться те же принципы, но казнь сложнее.

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


Для одностороннего потока (prod repo -> cleaned repo) вы можете сохранить один репо как с «оригинальной», так и с «очищенной» историей. Это может быть собственное производственное репо или специальный «мостовой репозиторий». (Это не может быть очищенный репозиторий, поскольку он будет содержать большую историю, которую вы пытаетесь удалить из него.)

Как именно добраться до того состояния, в котором вы находитесь, зависит от деталей вашего местонахождения. Для наглядности, если вы начали с такого подхода, он может выглядеть так:

У вас есть репо в <prod-url>. Вы клонируете его, и этот клон будет использоваться для создания хранилища моста.

$ git clone `<prod-url>` bridge
$ cd bridge

Вы запускаете BFG в bridge, а затем клонируете это, чтобы создать настоящий «чистый» репозиторий. Затем (снова в bridge) вы переконфигурируете origin, чтобы его ветви можно было сопоставить с пространством имен prod в репо bridge.

$ git config remote.origin.fetch refs/heads/*:refs/heads/prod/*

Теперь, когда вы извлекаете данные из источника в репозиторий моста, вместо обновления ссылок на удаленное отслеживание, git будет пытаться продвинуть набор ветвей в пространстве имен prod/. Но вы не хотите, чтобы эти ветки prod/* были загружены в ваш чистый репозиторий; самый простой способ исправить это - переместить очищенные ветви в пространство имен clean/ и перенастроить чистое хранилище, чтобы получать только ветви clean/*.

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

$ git checkout master
$ git checkout -b clean/master
$ git branch -D master

Для многих веток вы можете написать это (возможно, используя git for-each-ref, чтобы начать работу). Или вы могли бы каким-то образом злоупотреблять механизмом резервного копирования filter-branch.

В любом случае, как только ветви переместятся, перейдите в репо и

$ git config remote.origin.fetch +refs/heads/clean/*:refs/remotes/origin/*

Теперь, сделав шаг назад, в отличие от этой последней команды, когда я дал извлечение refspec для origin в репозитории на мосту , я опустил ведущий +, который часто используется в извлечении refspecs; это означает, что если ветвь prod претерпевает переписывание истории, извлечение будет жаловаться, и вы будете знать, что у вас есть потенциальная головная боль, которую нужно решить. Подробнее об этом позже.

Итак, в бридж-репо вы можете запустить

$ git fetch origin

, который повторно загрузит исходные ветви в пространстве имен prod/.

Теперь у вас есть как исходные ветви (например, refs/heads/prod/master), так и чистые ветви (например, refs/heads/clean/master). Можно нарисовать вот так

A' -- B' -- C' -- D' <--(clean/master)

A -- B -- C -- D <--(prod/master)

Истории не связаны, и вам нужно сохранить это таким образом. Но вы также хотите «знать», что ветка clean/master «обновлена» посредством D коммита на prod/master таким образом, чтобы упростить объединение будущих изменений. Один из способов - создать две дополнительные ветви - назовем их bridge-prod и bridge-clean.

Ветвь bridge-clean будет указывать на последний коммит, в который мы внесли изменения с prod. Новые изменения могут происходить в самих ветвях clean/, но bridge-clean будет помнить, как будет выглядеть очищенная версия prod.

$ git checkout clean/master
$ git branch bridge-clean

Тогда задача bridge-prod состоит в том, чтобы иметь то же содержимое, что и bridge-clean, до тех пор, пока он не получит новые изменения от prod/master - после чего он будет использоваться в качестве справочного для обновления bridge-clean еще раз.

Итак, чтобы инициализировать это, мы создаем копию D', чей родитель D.

git checkout prod/master
git checkout -b bridge-prod
git rm -r ':/'
git checkout bridge-clean -- ':/'
git commit

Теперь у вас есть

A' -- B' -- C' -- D' <--(bridge-clean)(clean/master)

                  D" <--(bridge-prod)
                /
A -- B -- C -- D <--(prod/master)

, где D' и D" имеют идентичное содержимое (это «очищенная» версия D). Поскольку D" имеет D в качестве родителя, вы можете объединить будущие изменения из prod/master в bridge-prod (D будет основой объединения). Итак, через некоторое время у вас есть

                     ... x <--(clean/master)
                    /
A' -- B' -- C' -- D' <--(bridge-clean)

                  D" <--(bridge-prod)
                /
A -- B -- C -- D ... H <--(prod/master)

Два ... могут включать в себя множество коммитов, веток, слияний, чего угодно; это не имеет большого значения. Важно то, что bridge-prod и bridge-clean по-прежнему представляют последнюю интеграцию между репозиториями.

Итак, затем вы хотите объединить prod/master с bridge-prod.

                     ... x <--(clean/master)
                    /
A' -- B' -- C' -- D' <--(bridge-clean)

                 D" -- H"<--(bridge-prod)
                /     /
A -- B -- C -- D ... H <--(prod/master)

Вы хотите, чтобы H" представлял очищенное состояние H. Для этого есть два условия:

Если ветвь prod/master обновляет файл, который был удален при очистке, объединение будет конфликтовать. К счастью, эти удаления являются единственными изменениями на «нашей» стороне слияния, и мы знаем, что хотим сохранить их поверх того, что prod/master могло бы сделать с этими файлами. Поэтому, когда мы сливаемся, мы можем сказать

git checkout bridge-prod
git merge -X ours prod/master

Параметр -X ours не следует путать с -s ours. В то время как -s ours будет использовать «нашу стратегию слияния», полностью игнорируя изменения prod/master, -X ours использует стратегию слияния по умолчанию с «опцией нашей стратегии» (спасибо, git, за наименование clear-as-mud) .

Это означает, что эта команда будет пытаться объединиться как обычно, но каждый раз, когда возникает конфликт, версия этого фрагмента кода bridge-prod имеет преимущественную силу. Поскольку единственные изменения в bridge-prod - удаление ненужных нам файлов, это хорошо.

Другая проблема была бы, если бы prod/master мог добавить новый файл, который должен быть исключен из очистки. Если вы знаете, что это не может произойти, нет проблем. Если это может произойти, то вам нужно проверить это. Например, перед объединением вы можете сказать

git diff prod/master prod/master^

и посмотрите, есть ли новые файлы, которые вам не нужны в чистом репо. Если так, то для вашего слияния сделайте

git checkout bridge-prod
git merge -X ours --no-commit prod/master
# remove the unwanted files
git add ':/'
git commit

Теперь, поскольку D" - это то же содержимое, что и D', это означает, что H" имеет TREE, который вы хотите в следующем bridge-clean коммите.

git checkout bridge-clean
git rm -r ':/'
git checkout bridge-prod -- ':/'
git commit

Это дает вам

                     ... x <--(clean/master)
                    /
A' -- B' -- C' -- D' -- H' <--(bridge-clean)

                 D" -- H"<--(bridge-prod)
                /     /
A -- B -- C -- D ... H <--(prod/master)

H' имеет то же содержимое, что и H" - это очищенное содержимое, обновляемое через H. Кроме того, H' имеет очищенную историю (ее родитель - D', которую мы очистили с самого начала), поэтому ее можно смело включать в чистое репо. Вы можете объединить bridge-clean с master и передача изменений завершена.

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

Однако это односторонний мост. Если вы объедините bridge-prod обратно в prod/master, вы почти наверняка удалите файлы, которые хотите сохранить в prod/master.

Если вам нужно взять изменения из чистого репо и применить их к prod-репо, вы можете сгенерировать патч для чистого репо. Несмотря на то, что контент чистого репо является подмножеством контента prod repo, патч должен применяться без особых хлопот. Это может вызвать некоторые ложные конфликты в следующий раз, когда вы объедините изменения из prod в clean.

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

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