Встановлення батьківського вказівника git для іншого батьківського


220

Якщо в минулому у мене є зобов’язання, яке вказує на одного з батьків, але я хочу змінити батьків, на який він вказує, як би я це зробив?

Відповіді:


404

Використання git rebase. Це узагальнена команда "взяти фіксацію (-ла) та спланувати її / їх на іншій батьківській (базовій)" команді в Git.

Однак слід знати кілька речей:

  1. Оскільки присвоєння SHA залучає своїх батьків, коли ви змінюєте батька певного комітету, його SHA змінюватиметься - як і SHAs усіх зобов’язань, які вступають після нього (новіші за нього) у лінію розвитку.

  2. Якщо ви працюєте з іншими людьми, і ви вже підштовхнули спірний комітет до місця, де вони його перетягнули, модифікація комісії, ймовірно, погана ідея ™. Це пов’язано з №1, і, таким чином, виникає плутанина у сховищах інших користувачів, коли вони намагатимуться з’ясувати, що сталося через те, що ваші SHA більше не відповідають їхнім "цим" комісіям. (Докладні відомості див. У розділі "ВІДНОСНЕННЯ З ВИПУСКУВАННЯ СКОРОСТІ" на пов'язаній сторінці підручника.)

Це означає, що якщо ви зараз перебуваєте у відділенні з деякими зобов’язаннями, які потрібно перейти до нового батька, це виглядатиме приблизно так:

git rebase --onto <new-parent> <old-parent>

Це перемістить все після того, як<old-parent> у поточній гілці <new-parent>замість цього буде сидіти .


Я використовую git rebase - для того, щоб змінити батьків, але, здається, я закінчую лише одну команду. Я поставив на це питання: stackoverflow.com/questions/19181665/…
Едуардо Мелло

7
Ага, так ТО для чого --ontoвикористовується! Документація для мене завжди була абсолютно незрозумілою, навіть після прочитання цієї відповіді!
Даніель К. Собрал

6
Цей рядок: git rebase --onto <new-parent> <old-parent>Мені сказати, що про те, як використовувати rebase --ontoвсі інші запитання та документи, які я прочитав, не вдалося.
jpmc26

3
Для мене ця команда повинна читати: rebase this commit on that oneабо git rebase --on <new-parent> <commit>. Використовувати старий батьків тут не має сенсу.
Самір Агіяр

1
@kopranb Старий батько важливий, коли ви хочете відкинути частину шляху від голови до віддаленої голови. Справа, яку ви описуєте, займається git rebase <new-parent>.
clacke

35

Якщо виявиться, що вам потрібно уникати повторного використання наступних комітетів (наприклад, тому, що перезапис історії буде неможливим), ви можете використовувати заміну git (доступну в Git 1.6.5 і пізніших версіях).

# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.

replace_first_parent() {
    old_parent=$(git rev-parse --verify "${1}^1") || return 1
    new_parent=$(git rev-parse --verify "${2}^0") || return 2
    new_commit=$(
      git cat-file commit "$1" |
      sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
      git hash-object -t commit -w --stdin
    ) || return 3
    git replace "$1" "$new_commit"
}
replace_first_parent B A

# …---o---A---o---o---…
#          \
#           C---b---b---…
#
# C is the replacement for B.

Якщо встановлена ​​вищезамінна заміна, будь-які запити на об’єкт B фактично повернуть об’єкт C. Вміст C точно такий же, як і вміст B, за винятком першого батька (ті самі батьки (крім першого), те саме дерево, те ж повідомлення про здійснення).

Заміни активні за замовчуванням, але їх можна ввімкнути, скориставшись --no-replace-objectsопцією git (перед назвою команди) або встановивши GIT_NO_REPLACE_OBJECTSзмінну середовища. Заміни можна розділити натисканням refs/replace/*(крім звичайного refs/heads/*).

Якщо вам не подобається зміна комісій (зроблено з sed вище), тоді ви можете створити свою заміну заміни, використовуючи команди вищого рівня:

git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -

Велика різниця полягає в тому, що ця послідовність не поширює додаткових батьків, якщо B є об'єктом об'єднання.


А потім, як би ви підштовхнули зміни до наявного сховища? Здається, працює локально, але git push після цього нічого не підштовхує
Baptiste Wicht

2
@BaptisteWicht: заміни зберігаються в окремому наборі списків. Вам потрібно буде домовитись просунути (і отримати) refs/replace/ієрархію оновлень. Якщо вам потрібно зробити це один раз, ви можете зробити це git push yourremote 'refs/replace/*'у вихідному сховищі та git fetch yourremote 'refs/replace/*:refs/replace/*'в сховищах призначення. Якщо вам потрібно зробити це кілька разів, ви можете замість цього додати ці параметри до remote.yourremote.pushзмінної конфігурації (у вихідному сховищі) та remote.yourremote.fetchзмінної конфігурації (у сховищах призначення).
Кріс Джонсен

3
Простіший спосіб зробити це - використовувати git replace --grafts. Не впевнений, коли це було додано, але вся його мета - створити заміну, яка є такою ж, як виконувати B, але з вказаними батьками.
Пол Вагленд

32

Зауважте, що для зміни коміту в Git потрібно змінити всі зобов’язання, що слідують за ним. Це відлякує, якщо ви опублікували цю частину історії, і хтось, можливо, побудував би свою роботу над історією, яка була до змін.

Альтернативним рішенням, git rebaseзгаданим у відповіді Амбер, є використання механізму трансплантатів (див. Визначення Git-трансплантатів у Глосарії Git та документація .git/info/graftsфайлу в документації щодо макета Git Repository ), щоб змінити батьківську команду, переконайтесь, що вона зробила виправлення з деяким переглядачем історії ( gitk, git log --graphтощо), а потім скористайтеся git filter-branch(як описано в розділі "Приклади" своєї сторінки), щоб зробити його постійним (а потім видаліть трансплантат і, можливо, видаліть оригінали, підкріплені резервним git filter-branchкодом, або повторно сховивши сховище):

echo "$ commit-id $ graft-id" >> .git / info / grafts
git filter-branch $ graft-id..HEAD

ПРИМІТКА !!! Це рішення відрізняється від перебазування рішення в тому , що git rebaseб перебазуватися / пересадки зміни , в той час як трансплантати на основі рішення просто Reparent фіксацій як , не беручи до уваги відмінності між старим батьком і новим батьком!


3
З того, що я розумію (з git.wiki.kernel.org/index.php/GraftPoint ), git replaceвитіснили щеплення щеплення (якщо припустити, що у вас git 1.6.5 або пізнішої версії).
Олександр Птах

4
@ Thr4wn: В основному це правда, але якщо ви хочете переписати історію, то спосіб зробити це щеплення + git-filter-branch (або інтерактивна база даних, або швидкий експорт + напр. Reposurgeon). Якщо ви хочете / потрібно зберегти історію , то git-substituна набагато перевершує трансплантати.
Якуб Нарбський

@ JakubNarębski, могли б ви пояснити , що ви маєте в виду переписування проти збереження історії? Чому я не можу використовувати git-replace+ git-filter-branch? У моєму тесті, filter-branchздавалося, поважають заміну, випустивши все дерево.
cdunn2001

1
@ cdunn2001: git filter-branchпереписує історію з повагою до трансплантатів та замін (але в переписаній історії заміни будуть суперечливими); натискання призведе до зміни, які не рухаються вперед. git replaceстворює замінні передавальні заміни; натискання буде швидким вперед, але вам потрібно буде натиснути, refs/replaceщоб перенести заміни, щоб виправити історію. HTH
Jakub Narębski

23

Щоб уточнити наведені відповіді та безсоромно підключити власний сценарій:

Це залежить від того, хочете ви "перезавантажити" чи "повторно". Перебазуватися , як це було запропоновано Амбер , рухається навколо подивитися відмінності . Reparent , як це було запропоновано Якуба і Кріс , рухається навколо знімків з цілого дерева. Якщо ви хочете переглянути його, я пропоную використовувати, git reparentа не робити роботу вручну.

Порівняння

Припустимо, у вас є зображення ліворуч, і ви хочете, щоб воно виглядало як зображення праворуч:

                   C'
                  /
A---B---C        A---B---C

Як скидання, так і повторна редагування створюють однакову картину, але визначення C'відрізняється. З git rebase --onto A B, C'не буде містити жодних змін, внесених користувачем B. З git reparent -p A, C'буде ідентичним C(крім того B, що не буде в історії).


1
+1 я експериментував зі reparentсценарієм; це прекрасно працює; настійно рекомендується . Я також рекомендую створити нову branchмітку та до checkoutцієї гілки перед викликом (оскільки мітка гілки буде переміщуватися).
Ревінь

Варто також зазначити, що: (1) вихідний вузол залишається недоторканим, і (2) новий вузол не успадковує дітям початкового вузла.
Ревінь

0

Однозначно @ відповідь Якуба допомогла мені кілька годин тому, коли я намагався точно так само, як і ОП.
Однак git replace --graftзараз простіше рішення щодо трансплантатів. Крім того, головна проблема з цим рішенням полягала в тому, що фільтр-гілка змусив мене звільнити кожну гілку, яка не була об'єднана у гілку HEAD. Тоді, git filter-repoробив роботу ідеально та бездоганно.

$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force

Для отримання додаткової інформації: ознайомтеся з розділом "Історія повторного щеплення" в документах

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.