2014-08-29 3 views
3

Я имею проблемы очистки моей истории мерзавец дерево ..Git филиал точка разделения

В основном мой мерзавец репозиторий с 1 филиал выглядит следующим образом:

A--B--C--D--E--F--G--H--I--Z--X--Y... master 
      \ 
      F--G--H--I--J--K--L...  branch1 

Это обязательство Е является некоторые не важные изменения и можно сделать излишним. Правильная точка разделения здесь должна быть не D. Это фиксация E может быть либо удалена, либо добавлена ​​в обе ветви.

Есть ли способ, которым это может быть достигнуто?

----- Обновление

Wow Большое спасибо за вашу помощь!
Я пробовал, что предлагал торек, и это работало как шарм!

Теперь .. У меня есть еще один репозиторий для очистки, но это более сложный ..

Это выглядит следующим образом:

1--2--3--4--*A--5--6--7--8--*B--9--10--11--*C--*D--12--13--14   master 
      \ 
      5--6--7--8--9--10--11--12--13--14        branch1 

Note1: буквы представляют содержание фиксации
Примечание 2: * письмо commits только для ведущей ветки

Есть ли способ очистить дерево истории, как это?

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

ответ

3

Во-первых, +1 на вопрос о графике фиксации. :-) Во-вторых, не очень важно, но вы должны подумать об этом как о двух ветвях: master и branch1. Нет ничего особенного в master, это просто ветка, как и любая другая.

Теперь, в центре всего, что находится в git, это факт, что вы никогда не сможете изменить фиксацию.Это происходит из того, как git на самом деле называет каждый фиксатор, с использованием большого уродливого 40-символьного SHA-1: SHA-1 производится путем вычисления криптографической контрольной суммы содержимого коммита. Поэтому, если вы (попытаетесь) изменить что-либо, изменится «истинное имя», и у вас будет новая и другая фиксация.

Вместо этого вы можете сделать копию старой фиксации на новую, внесение желаемых изменений по пути непосредственно перед завершением коммита (и, следовательно, с учетом его «истинного имени»). Мы сделаем копии.

Вы нарисовали этот график:

A--B--C--D--E--F--G--H--I--Z--X--Y... master 
      \ 
      F--G--H--I--J--K--L...  branch1 

, но это не может быть вполне правы, потому что при каждой фиксации точки родительскому фиксации, и здесь F точки обратно E, когда мы смотрим на master , но F указывает на D, если посмотреть на branch1. (Я предполагаю здесь, что каждая отдельная буква стоит за SHA-1 «истинное имя» данного коммита.) Я собираюсь предположить, что те, которые в настоящее время находятся на branch1, являются копиями, хотя это не имеет большого значения который, пока вы довольны деревьями, связанными с этими коммитами (то есть, что вы получаете, когда один из них git checkout).

То, что вы хотите выглядеть следующим образом:

A--B--C--D--E--F--G--H--I--Z--X--Y... master 
         \ 
          J'-K'-L'... branch1 

Здесь "премьер" в знак J', K' и L' показывают, что эти копии фиксаций J, K и L.

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

Начнем с того, что сообщило rebase для работы на branch1; простой способ заключается в git checkout branch1 (или мы могли бы просто добавить branch1 в качестве последнего аргумента в git rebase, но я буду использовать «простой способ»).

Далее, нам нужно знать, где мы хотим, чтобы коммиты отделились, т. Е. Имели некоторый способ назвать фиксацию I. Из вышесказанного мы могли бы сказать master~3: счетчик назад совершает четыре фиксации от master (имена которых фиксируют Y), и вы проходите через X, затем Z, затем достигаете I. Или вы можете сделать это с помощью истинного SHA-1, который всегда работает, но часто требует вырезания и вставки, чтобы получить право. Для приведенной ниже команды я просто напишу I. Мы укажем rebase на базу --onto, которая зафиксирует.

Наконец, мы должны получить rebase, чтобы выбрать коммиты J, K и L для копирования. Есть множество способов сделать это. Возможно, самым простым является использование git rebase -i, которое предложит переустановить все на branch1, а затем вы можете удалить строки pick для первых четырех коммитов.Или, мы можем сказать rebase, чтобы исключить определенные коммиты, но я предполагаю, что вы будете использовать интерактивный метод.

Таким образом, окончательная команда здесь git rebase -i --onto I master (после выполнения команды git checkout branch1). -i делает это интерактивным, так что вы можете сбросить коммиты; --onto выбирает цель для новой серии фиксаций, которую rebase будет вишневым; и часть master сообщает rebase, которая обязывает исключать: в частности, любые фиксации достижимы от имени master. (Это исключает совершает A, B, C и D, но не скопированные версии F', G', H' и I', которые появляются на branch: вы просто удалить «подцепить» линию для тех, кто).

После того, как git rebase закончил свою команду git cherry-pick, последнее, что он делает, - это переместить ярлык ветки для вашей текущей ветви (branch1) на самую новую команду. Таким образом, это дает:

A--B--C--D--E--F--G--H--I--Z--X--Y... master 
         \ 
          J'-K'-L'... branch1 

при условии, что все идет хорошо. (. Если она идет плохо, вы можете git rebase --abort, чтобы остановить процесс и поставить все обратно, как это было прежде, чем вы начали)


Мы могли бы быть фантазером и (при условии, I' это branch1~3) сделать:

git rebase --onto master~3 branch1~3 branch1 

без каких-либо начальных git checkout, но это предполагает, что отсчет 3 здесь верен (дляи branch1). Это в основном так же, как и раньше, с тремя модификациями:

  1. добавить branch1 на конце, чтобы сделать rebase проверить его в качестве первого шага
  2. мы используем branch1~3 вместо master в качестве аргумента, который говорит «исключить этот материал, только перебазироваться вещи, которые не достижимы отсюда»
  3. мы опускаем -i, так как нам не нужно редактировать прочь„выбрать“команды на этот раз

Это требует аЬ это более тщательный подсчет коммитов, чтобы убедиться, что все выражения ~ правы (или, опять же, вы можете работать с ними с помощью сырых идентификаторов SHA-1).


Ну, есть несколько специальных вещей о master: для одной стороны, это отделение вы надеть, когда вы делаете git init и это создает новое хранилище. Другим является то, что git merge слегка изменяет сообщение о слиянии. Но оба они довольно тривиальны.

Это действительно справедливо для всех четырех объектов, найденных в репозитории git (commit, tree, аннотированный тег и «blob» - последний хранит содержимое файла). Каждый хранится в репозитории, а затем задается имя, которое состоит из собственной криптографической контрольной суммы.

Более точно, каждый фиксации имеет ноль или более строк parent ... в ней, с каждым parent давая SHA-1 идентификатор соответствующего родительского фиксации. Первый родитель, как правило, является «главной линией» ветви, и все дополнительные родители указывают, что это коммит слияния. Конец без родителей, например A, является «корневой фиксацией».

+0

Почему бы не просто 'git checkout branch1', а' git rebase COMMIT_HASH_FOR_I'? Или просто 'git rebase COMMIT_HASH_FOR_I branch1'? ('COMMIT_HASH_FOR_I' для версии в master). Я думаю, что это немного более прямолинейно, хотя ваша версия является полной, канонической формой. :-) Хороший ответ, кстати. Очень полно. – jszakmeister

+0

@jszakmeister: Я не пробовал, но думаю (предполагая, что исходный граф должен читать 'F'-G'-H'-I '-...' простой 'git rebase ' скопируйте 'F'' в' F''' и т. д., и не сможете увидеть, что они уже находятся. Rebase действительно знает, что в некоторых случаях удалять одинаковые коммиты, поэтому может работать. – torek

+0

Пока diff то же самое, тогда и патч-идентификатор будет, и Git обязательно их убьет. Это становится проблемой только в том случае, если 'F'',' G'' и 'H'' имеют дополнительные модификации из-за конфликта или изменения в некоторых Кстати, FWIW, я сделал это более чем несколько раз. :-) – jszakmeister

1

Один из способов сделать это, чтобы создать новую ветку в качестве копии мастера и сделать git reset --hard I-commit, а затем cherry-pick совершающее вас хотят (надеясь, что не слишком много)

+0

На мой взгляд, лучше создать непосредственно новую ветку, начиная с I, вместо того, чтобы делать такую ​​команду сброса. –

2

Вы могли бы сделать новый филиал в I и cherry pick все изменения, сделанные в branch1 поэтому в основном

git branch -b temp I 
git cherry-pick J..branch1 

затем у вас будет новый Филиал temp с разделом на I без изменений E и содержащий все фиксации branch1, начиная с J, если это сработало правильно, вы можете переименовать эти два, чтобы сделать temp ветви вашего нового branch1. Перед сборкой вишни вы можете вернуть фиксацию E, если вы не хотите ее в branch1

+0

Я просто изменил вашу первую команду и заменил ее на следующую: 'git checkout -b temp I'. Используя эту команду, вы будете автоматически на соответствующей ветке, чтобы выполнить следующие предоставленные команды. –

+0

@ JoëlSalamin done – Trudbert

+0

Это решение теперь является хорошим способом решения описанной проблемы начальной точки перехода, но 'git revert E'is не то, что просит @hoistyler! Эта команда создаст новый коммит, который будет идентичен E, и изменения F, G и H будут потеряны! –