Как мне объединить подкаталог в Git?


86

Можно ли объединить только изменения для подкаталога из локальной ветки Git в удаленную ветку Git, или это «все или ничего»?

Например, у меня есть:

branch-a
 - content-1
 - dir-1
   - content-2

и

branch-b
 - content-1
 - dir-1
   - `content-2

Я хочу только объединить содержимое branch-a dir-1 с содержимым branch-b dir-1.


1
Я думаю, что это дубликат: stackoverflow.com/questions/449541/…
Карл Фойгтланд

Ответы:


81

В качестве альтернативы вопросу SO « Как объединить отдельные файлы с помощью git-merge? » Я нашел этот поток GitHub, который можно было бы более приспособить для слияния всего подкаталога на основе дерева чтения git :

  • Мой репозиторий => cookbooks
    Целевой каталог моего репозитория =>cookbooks/cassandra
  • Удаленный репозиторий => infochimps
    Источник удаленного репозитория, который я хочу объединить cookbooks/cassandra=>infochimps/cookbooks/cassandra

Вот команды, которые я использовал для их объединения

  • Добавьте репозиторий и загрузите его
git удаленное добавление -f infochimps git: //github.com/infochimps/cluster_chef.git
  • Выполните слияние
git merge --allow-unrelated-stories -s ours --no-commit infochimps / master

(это выполняет слияние с использованием стратегии 'ours' ( -s ours), которая отбрасывает изменения из исходной ветки. Это записывает факт infochimps/masterслияния без фактического изменения какого-либо файла в целевой ветке)

  • Слияние только infochimps/cookbooks/cassandraсcassandra
git read-tree --prefix = кассандра / -u infochimps / master: кулинарные книги / кассандра

Это считывает дерево только для необходимого подкаталога с исходным кодом, т. Е. В cookbooks/cassandraвосходящей ветке исходного репозитория.

Обратите внимание, что имя целевого подкаталога также должно быть cookbooks/cassandra, иначе вы увидите:

fatal: Not a valid object name
  • Зафиксируйте изменение
 git commit -m 'слияние в infochimps cassandra'

Дополнение

Это странно, [отредактируйте меня] - но read-treeшаг может потерпеть неудачу вот так:

error: Entry 'infochimps/cookbooks/cassandra/README' overlaps with 'cookbooks/cassandra/README'. Cannot bind.

... даже если оба файла идентичны . Это может помочь:

git rm -r cassandra
git read-tree --prefix=cassandra/ -u infochimps/master:cookbooks/cassandra

Но, конечно, проверьте вручную, что это делает то, что вы хотите.


3
@Martin git-scm.com/docs/git-rev-parse#_specifying_revisions ищет <rev>:<path>, например HEAD:README, :README,master:./README
VonC

6
git read-treeШаг терпит неудачу для меня:error: Entry 'foo/bar/baz.php' overlaps with 'bar/baz.php'. Cannot bind.
Weston Ruter

1
@VonC, но нет, мне нужна история. Этот ответ не учитывает случай, когда в файлы в дереве вносятся локальные изменения. Значит, нужно слить.
Уэстон Рутер,

У меня overlaps withуказанная выше ошибка тоже выскакивает на идентичных файлах . Насколько это странно?
ulidtko

1
@ChrisHalcrow Чтобы записать факт infochimps/masterслияния, но без фактического изменения какого-либо файла в целевой ветви. Потому что следующим шагом git read-tree --prefix=cassandraбудет модификация. Последняя фиксация запишет фактическое содержимое «слияния».
VonC

31

В моем примере предположим, что у вас есть ветка «источник» и ветка «место назначения», которые оба отражают исходные версии самих себя (или нет, если только локальные) и подтягиваются к последнему коду. Допустим, мне нужен подкаталог в репозитории с именем newFeature, который существует только в ветке «исходный код».

git checkout destination
git checkout source newFeature/
git commit -am "Merged the new feature from source to destination branch."
git pull --rebase
git push

Это значительно менее запутано, чем все остальное, что я видел, и это отлично сработало для меня, найдено здесь .

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


2
Сохраняет ли он историю?
Bibrak

2
@Bibrak этот подход НЕ сохраняет историю. По крайней мере, не с текущими командами.
Рави Гидвани

6

Я получил это из ветки форума в Eclipse, и это сработало как шарм:

git checkout source-branch
git checkout target-branch <directories-or-files-you-do-**NOT**-want> 
git commit
git checkout target-branch
git merge source-branch

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

2
Это не сливается, а заменяет папку папкой из исходной ветки.
Gp2mv3

@ Gp2mv3 Думаю, выглядит солидно. checkoutделает папки одинаковыми => разница только в разложении checkoutпапок => mergeразница. Это действенная стратегия.
Caveman

6

Учитывая сценарий OP, где у них есть две ветки, но они хотят объединить только историю dir-1 из ветки-a в ветку-b :

# Make sure you are in the branch with the changes you want
git checkout branch-a

# Split the desired folder into its own temporary branch
# This replays all commits, so it could take a while
git subtree split -P dir-1 -b temp-branch

# Enter the branch where you want to merge the desired changes into
git checkout branch-b

# Merge the changes from the temporary branch
git subtree merge -P dir-1 temp-branch

# Handle any conflicts
git mergetool

# Commit
git commit -am "Merged dir-1 changes from branch-a"

# Delete temp-branch
git branch -d temp-branch

1

Используйте, git cherry-pickчтобы выбрать нужные коммиты и объединить только эти коммиты. Ключевой трюк здесь - получить эти коммиты простым способом (чтобы вам не приходилось выяснять их, вручную проверяя журнал Git и вводя их вручную). Вот как: используйте git logдля вывода идентификатора SHA-1 фиксации , например:

git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>

«commit-a» - это фиксация непосредственно перед начальной точкой ветви для слияния, а «commit-b» - это последняя фиксация в ветке для слияния. '--reverse' печатает эти коммиты в обратном порядке для дальнейшего использования.

Тогда сделайте это так:

git cherry-pick $(git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>)

Это два шага, простой и стабильный!


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

1
Предполагается, что найденные коммиты влияют только на рассматриваемый каталог. Если фиксация влияет как на каталог, который вы хотите объединить, так и на те, которые вы не хотите, это будет слишком много.
Скотт

Two steps, simple and stable!совсем не просто :)
Рафа

Как вы думаете, что это просто? @ Рафа
Роберт

@ Роберт Я не имел в виду, что ваш ответ непростой; Виной всему gitужасный интерфейс и ужасная ментальная модель, полная загадок, случайных имен и значений.
Рафа

0

Создайте репозиторий Git, содержащий как branch-a, так и branch-b:

git checkout branch-a
git diff branch-b dir-1 > a.diff
patch -R -p1 < a.diff

14
Для этого ответа требуется дополнительная информация. Что из этого фактический код против комментариев?
qodeninja

2
Инициатор запроса хочет выполнить слияние. Использование патча для переноса изменений автоматически объединяет все коммиты в один патч, и история теряется.
Эрик
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.