Как сделать существующий филиал сиротой в git

Есть ли способ сделать существующую ветку сиротой в git?

git checkout --orphan кажется, только создает новую сироту?

Ответы

Ответ 1

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

Сначала вам нужно выбрать фиксацию, чтобы начать новую ветку. В моем примере это будет HEAD~2, sha1=df931da.

Скажем, у нас есть простой репо. git log --oneline --graph --decorate показывает следующее:

* 4f14671 (HEAD, master) 4
* 1daf6ba 3
* df931da 2
* 410711d 1

Теперь, действие!

# Move to the point where we want new branch to start.
➜  gitorphan git:(master) git checkout HEAD~2

Здесь и далее часть ➜ gitorphan git:(master) представляет собой запрос zsh, а не часть команды.

# make an orphan branch
➜  gitorphan git:(df931da) git checkout --orphan orphanbranch
Switched to a new branch 'orphanbranch'

# first commit in it
➜  gitorphan git:(orphanbranch) ✗ git commit -m'first commit in orphan'
[orphanbranch (root-commit) f0d071a] first commit in orphan
 2 files changed, 2 insertions(+)
 create mode 100644 1
 create mode 100644 2

# Check that this is realy an orphan branch
➜  gitorphan git:(orphanbranch) git checkout HEAD^
error: pathspec 'HEAD^' did not match any file(s) known to git.

# Now cherry-pick from previous branch a range of commits
➜  gitorphan git:(orphanbranch) git cherry-pick df931da..master
[orphanbranch 7387de1] 3
 1 file changed, 1 insertion(+)
 create mode 100644 3
[orphanbranch 4d8cc9d] 4
 1 file changed, 1 insertion(+)
 create mode 100644 4

Теперь ветвь orphanbranch имеет моментальный снимок рабочего дерева в df931da в одном коммите и далее совершает то же, что и в главном.

➜  gitorphan git:(orphanbranch) git log --oneline
4d8cc9d 4
7387de1 3
f0d071a first commit in orphan

Ответ 2

Вы правы, что git checkout --orphan создает только новые ветки-сироты. Фокус в том, что этот процесс не меняет индекс. Таким образом, Ответ Ника Волынкина будет работать, пока ваш Git не слишком древний.

Если вы хотите сохранить исходное сообщение фиксации, вы можете заменить его:

$ git commit -m'first commit in orphan'

с:

$ git commit -C master~2

Если ваш Git достаточно старый, что у вас нет git checkout --orphan, это также должно сделать это:

$ commit=<hash>  # or, e.g., commit=$(git rev-parse master~2)
$ git branch newbranch $( \
    git log --no-walk --pretty=format:%B $commit | \
    git commit-tree -F - "${commit}^{tree}" \
)
$ git checkout newbranch
$ git cherry-pick $commit..master # may want -x; see below

где вы выбираете начальную точку из git log или используя синтаксис ~ с именем существующей ветки (это продолжает использовать master~2, как в ответе Ника).

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

Вещи, которые вам нужно знать о ветвях

Прежде чем идти дальше, вам кажется, что это хорошая идея, чтобы определить некоторые элементы и описать, что происходит.

Имена ветвей и граф фиксации

Сначала дайте четкое различие между именем ветки, например master или newbr, и различными частями графа фиксации. Имя ветки просто указывает на одну фиксацию, обозначенную подсказкой наконечника или ветвью, внутри графика:

*--o--o---o--o    <-- master
    \    /
     o--o--o--o   <-- brA
            \
             o    <-- brB

Этот граф имеет три ответвления, на которые указывают master, brA и brB. Например, родословная вершины brB восходит по вигглистой линии, всегда перемещаясь влево и иногда вверх, в (одиночный) корневой фиксатор * (в отличие от всех остальных non-root o > коммиты). Тот факт, что commit * не имеет коммитов в его левую сторону - ни один из родителей не совершает привязку к точкам - это то, что делает его фиксацией root.

Этот корневой фиксатор находится на всех ветвях. Другие коммиты также находятся на нескольких ветвях. Там происходит объединение слиянием на master, которое приносит коммиты из brA внутри, даже если brA имеет две коммиты, которые master не выполняет, например. Чтобы следовать за master назад к корню, вы должны идти прямо влево, а также вниз и влево при слиянии, а затем вернуться вверх и вниз, где brA отпадает.

Обратите внимание, что мы можем иметь несколько названий ветвей, указывающих на одну фиксацию или имена ветвей, указывающие на "подсказку", которые встроены в другую ветвь:

*--o--o---o--o    <-- master
    \    /
     o--o--o      <-- brA
            \
             o    <-- brB, brC

Здесь мы "перематываем" ветвь brA с помощью одной фиксации, так что фиксация средней строки правой стороны является концом brA, даже если она вернется с кончика brB. Мы добавили новую ветвь brC, которая указывает на тот же фиксат, что и brB (сделав его совет дважды, давайте надеяться, что это коммит не является подсказкой в ​​британско-английском "совете мусора", смысл слова: "тьфу, это совершение является абсолютным советом!" ).

DAG

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

Узлы, а также направленные ссылки ребер от дочернего к родительскому, образуют граф фиксации. Поскольку этот график направлен (от дочернего к родительскому) и ацикличен (после того, как вы отпустите node, вы никогда не сможете вернуться к нему), это называется D, направленным A циклический G raph или DAG. DAG имеют все виды хороших теоретических свойств, большинство из которых мы можем игнорировать для этого SO-ответа.

DAG могут иметь отключенные подграфы

Теперь рассмотрим этот альтернативный график:

*--o--o---o--o   <-- master
    \    /
     o--o--o     <-- brA

*--o--o--o       <-- orph

Эта новая ветвь, совет которой называется orph, имеет свой собственный корень и полностью отключен от двух других ветвей.

Обратите внимание, что несколько корней являются необходимым предварительным условием наличия (непустых) непересекающихся подграфов, но в зависимости от того, как вы хотите просмотреть эти графики, их может быть недостаточно. Если мы должны были слить (tip commit of) brA в orph 1 мы получили бы это:

*--o--o---o--o   <-- master
    \    /
     o--o--o     <-- brA
            \
*--o--o--o---o   <-- orph

и два "фрагмента графов" теперь соединены. Однако существуют субграфы (например, те, которые начинаются с orph^1 и brA, двух родителей orph), которые не пересекаются. (Это не особенно важно для создания сиротских ветвей, это просто то, что вы должны понимать о них.)


1 Современный Git отвергает случайную попытку сделать такое слияние, так как у двух ветвей нет базы слияния. Старые версии Git выполняют слияние, не обязательно с разумными результатами.


git checkout --orphan

Разветвление orph - это вид ветки, который git checkout --orphan делает: ветвь, у которой будет новый, отключенный корень.

То, как это происходит, состоит в том, чтобы создать имя ветки, которое указывает на отсутствие фиксации вообще. Git называет это "нерожденной ветвью", а ветки в этом состоянии имеют только своеобразное полусуществование, потому что Git утечка реализации через.

Нерожденные ветки

Имя ветки, по определению, всегда указывает на фиксацию самого конца в этой ветки. Но это оставляет Git с проблемой, особенно в совершенно новом новом репозитории, который вообще не имеет никаких коммитов: где может master указать?

Дело в том, что нерожденная ветвь не может указывать нигде, и потому что Git реализует имена ветвей, записывая их как имя < имя, commit-ID > пара, 2 он просто не может записать ветвь до тех пор, пока не будет фиксация. Git решение этой дилеммы - обмануть: имя ветки вообще не входит в записи ветки, а вместо этого только в запись HEAD.

HEAD, в Git, записывает текущее имя ветки. Для режима "отсоединенный HEAD" HEAD записывает фактический идентификатор фиксации, и на самом деле это означает, что Git определяет, находится ли репозиторий/рабочее дерево в режиме "отсоединенный HEAD": если его файл HEAD содержит имя ветки, оно не отделяется, и если оно содержит идентификатор фиксации, оно отключается. (Никакие другие государства не разрешены.)

Следовательно, для создания "сиротской ветки" или в течение этого неудобного периода, когда еще нет фиксации для master, Git хранит имя в HEAD, но на самом деле не создает имя ветки. (То есть для него нет записи .git/refs/heads/, а для него нет строки в .git/packed-refs.)

В качестве своеобразного побочного эффекта это означает, что у вас может быть только одна неродившаяся ветвь. Имя неродившегося ветки хранится в HEAD. Проверка другой ветки с помощью или без --orphan или любого фиксации по идентификатору - любое действие, которое обновляет HEAD, - уничтожает все следы неродившегося ветки. (Новый git checkout --orphan, конечно, заменяет его на след новой нерожденной ветки.)

Как только вы делаете первый фиксатор, новая ветвь возникает, потому что...


2 С помощью "распакованных" ссылок имя - это всего лишь путь в файловой системе: .git/refs/heads/master. Идентификатор commit-is - это просто содержимое этого файла. Упакованные ссылки хранятся по-разному, а Git разрабатывает другие способы обработки сопоставления имен к идентификатору, но это самый простой и в настоящее время все еще необходимо для работы Git.

Существует два очевидных способа сохранить неродинные ветки, но Git не использует ни один из них. (Для записи это: создать пустой файл или использовать специальный "нулевой хеш". У пустой трюк файла есть очевидный недостаток: он будет очень хрупким перед лицом сбоев команды или компьютера, намного больше, чем использование нулевой хэш.)


Процесс фиксации

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

  • Обновить и/или заполнить индекс, также называемый промежуточной областью или кешем: git add различные файлы. Этот шаг создает объекты Git blob, которые сохраняют фактическое содержимое файла.

  • Запишите индекс в один или несколько объектов дерева (git write-tree). Этот шаг создает или, в редких случаях, повторное использование, по крайней мере, одного (верхнего уровня) дерева. Это дерево имеет записи для каждого файла и подкаталога; для файлов он отображает blob-ID и для подкаталогов, он перечисляет (после создания) дерево, содержащее файлы подкаталогов и деревья. Обратите внимание, кстати, что это оставляет индекс невозмутимым, готовым к следующей фиксации.

  • Напишите объект фиксации (git commit-tree). Этот шаг требует кучу предметов. Для наших целей основными интересными являются (единый) древовидный объект, который идет с этим фиксацией, - тот, который мы только что получили от шага 2, - и список идентификаторов родительских кодов.

  • Введите новый идентификатор фиксации в текущее имя ветки.

Шаг 4: как и почему имена ветвей всегда указывают на фиксацию наконечника. Команда git commit получает имя ветки от HEAD. Он также на этапе 3 получает первичный (или первый и только один) родительский код фиксации таким же образом: он читает HEAD, чтобы получить имя ветки, затем считывает идентификатор фиксации наконечника из ветки. (Для слияния коммитов он считывает дополнительные родительские идентификаторы - обычно один - от MERGE_HEAD.)

Git commit знает, конечно, о нерожденных и/или сиротских ветвях. Если HEAD говорит refs/heads/master, но ветвь master не существует... ну, тогда master должна быть нерожденной ветвью! Таким образом, у этого нового коммита нет родительского идентификатора. Он по-прежнему имеет то же дерево, что и всегда, но это новый корневой фиксатор. Он по-прежнему получает свой идентификатор, записанный в файл ветки, который имеет побочный эффект создания ветки.

Следовательно, он делает первую фиксацию на новой сиротской ветке, которая фактически создает ветвь.

Вещи, которые вам нужно знать о вишневом пике

Git cherry-pick команда очень простая в теории (практика иногда немного усложняется). Вернемся к нашим примерным графикам и проиллюстрируем типичную операцию вишневого захвата. На этот раз, чтобы поговорить о некоторых конкретных коммитах внутри графика, я дам им одноименные буквы:

...--o--o--A--B--C   <-- mong
      \
       o--o          <-- oose

Предположим, что мы хотели бы перевести commit B из ветки mong в ветвь oose. Это просто, мы просто делаем:

$ git checkout oose; git cherry-pick mong~1

где mong~1 обозначает commit B. (Это работает, потому что mong обозначает commit C, а C parent - B, а mong~1 означает "переместить один родительский фиксатор вдоль основной строки исходных ссылок. Точно так же mong~2 обозначает commit A и mong~3 обозначает o непосредственно перед A и т.д. Пока мы не совершаем транзакцию слиянием, в которой есть несколько родителей, здесь все очень просто.)

Но как работает git cherry-pick? Ответ: сначала он запускается git diff. То есть он создает патч, который показан как git log -p или git show.

Задания имеют полные деревья

Помните (из нашего предыдущего обсуждения), что каждая фиксация имеет прикрепленный объект дерева. Это дерево содержит все исходное дерево, как и для этой фиксации: моментальный снимок всего, что было в области index/staging, когда мы сделали это commit.

Это означает, что commit B имеет целое полное дерево работы, связанное с ним. Но мы хотим, чтобы вишня выбрала изменения, сделанные нами в B, а не дерево B. То есть, если мы изменили README.txt, мы хотим получить сделанное изменение: не старую версию README.txt, а не новую версию, просто изменения.

То, как мы находим это, состоит в том, что мы переходим от commit B к его родительскому объекту, который является commit A. Commit A также имеет полное полное дерево. Мы просто запускаем git diff на двух коммитах, что показывает нам, что мы изменили в README.txt, а также любые другие изменения, которые мы сделали.

Теперь, когда у нас есть diff/patch, мы вернемся к тому, где мы сейчас находимся: наконечник конца ветки oose и файлы, которые у нас есть в нашем дереве и в нашем индексе/промежуточной области, которые соответствуют к этой фиксации. (Команда git cherry-pick по умолчанию отказывается запускаться вообще, если наш индекс не соответствует нашему дереву, поэтому мы знаем, что они одинаковы.) Теперь Git просто применяется (как и к git apply), патч, который мы только что получили, с помощью различных коммитов A и B.

Следовательно, любые изменения, которые мы сделали, чтобы перейти от A в B, мы делаем их теперь, в наш текущий файл commit/index/work-tree. Если все идет хорошо, это дает нам измененные файлы, которые Git автоматически git add относятся к нашему индексу; а затем Git запускает git commit, чтобы сделать новую фиксацию, используя сообщение журнала из commit B. Если мы запустили git cherry-pick -x, Git добавляет фразу "cherry-pick from..." в наше новое сообщение журнала фиксации.

( Подсказка: обычно вы хотите использовать -x. Это, вероятно, должно быть по умолчанию. Главное исключение - когда вы не собираетесь сохранять исходный коммит, который вы только что выбрали из вишни Можно также утверждать, что использование cherry-pick обычно неверно - это указание на то, что вы сделали что-то неправильно раньше, на самом деле, и теперь им приходится печатать на бумаге, а перепечатка может не задерживаться в долгосрочной перспективе, - но это для другой [длинной] публикации полностью.)

Вишневый сбор в сиротской ветке

VonC отметил, что в Git 2.9.1 и более поздних версиях git cherry-pick работает в сиротской ветке; в предстоящем выпуске он работает как для последовательностей, так и для отдельных коммитов. Но есть причина, по которой это было невозможно так долго.

Помните, что cherry-pick превращает дерево в патч, отличая фиксацию против своего родителя (или, в случае слияния, родителя, который вы выбираете с опцией -m). Затем он применяет этот патч к текущему фиксации. Но сиротская ветвь - ветвь, которую мы еще не сделали, - не имеет никаких коммитов, следовательно, нет текущей фиксации и, по крайней мере, в философском смысле - нет индекса и рабочего дерева. Просто нечего исправлять.

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

Это то, что делает git checkout --orphan orphanbranch. Вы проверяете некоторые существующие фиксации и, следовательно, заполняете индекс и дерево работы. Затем вы git checkout --orphan newbranch и git commit, а новый commit использует текущий индекс для создания или фактического повторного использования дерева. Это дерево - это то же дерево, что и сделанное вами чек, прежде чем вы сделали git checkout --orphan orphanbranch. 3

Вот откуда взялась основная часть моего рецепта для очень старого Git:

$ commit=$(git rev-parse master~2)
$ git branch newbranch $( \
    git log --no-walk --pretty=format:%B $commit | \
    git commit-tree -F - "${commit}^{tree}" \
)
$ git checkout newbranch

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

Использование ${commit}^{tree} сообщает Git найти фактическое дерево, связанное с фиксацией (это стандартный gitrevisions синтаксис). Команда git commit-tree записывает новую фиксацию в репозиторий, используя это дерево, которое мы только что предоставили. Родитель нового коммита берутся из родительских идентификаторов, которые мы поставляем, используя параметры -p: мы используем none, поэтому новый коммит не имеет родителей, т.е. Является корневым фиксатором.

Сообщение журнала для этого нового коммита - это то, что мы поставляем на стандартном входе. Чтобы получить это сообщение журнала, мы используем git log --no-walk --pretty=format:%B, который просто печатает полный текст сообщения на стандартный вывод.

Команда git commit-tree выводит в качестве своего вывода идентификатор новой фиксации:

$ ... | git commit-tree "master~2^{tree}"
80c40c288811ebc44e0c26a5b305e5b13e8f8985

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

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


3 Фактически вы можете комбинировать их, как обычно:

git checkout --orphan orphanbranch master~2

который сначала проверяет (помещает в индекс и рабочее дерево) содержимое фиксации, идентифицированное master~2, затем устанавливает HEAD так, чтобы вы были на неродившейся ветке orphanbranch.


Использование git cherry-pick в сиротской ветки не так полезно, как нам бы хотелось

У меня есть более новая версия Git built (она терпит неудачу в некоторых своих тестах-умирает во время t3404-rebase-interactive.sh, но в остальном выглядит в основном ОК):

$ alias git=$HOME/.../git
$ git --version
git version 2.9.2.370.g27834f4

Пусть выберете с --orphan, master~2 с новым именем orphanbranch:

$ git checkout --orphan orphanbranch master~2
Switched to a new branch 'orphanbranch'
$ git status
On branch orphanbranch

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   .gitignore
    new file:   a.py
    new file:   ast_ex.py
[snip]

Поскольку это новая ветвь, она выглядит как Git, как будто все новое. Если я сейчас попробую git cherry-pick либо master~2, либо master~1:

$ git cherry-pick master~2
error: Your local changes would be overwritten by cherry-pick.
hint: Commit your changes or stash them to proceed.
fatal: cherry-pick failed
$ git cherry-pick master~1
error: Your local changes would be overwritten by cherry-pick.
hint: Commit your changes or stash them to proceed.
fatal: cherry-pick failed

Что мне нужно сделать, это очистить все, и в этом случае применение изменения от master~3 до master~2 вряд ли сработает или просто сделает начальный git commit в любом случае, чтобы сделать новую команду root на основе дерева из master~2.

Заключение

Если у вас есть git checkout --orphan, просто используйте это, чтобы проверить целевую фиксацию oldbranch~N (или с помощью идентификатора хэша, который вы можете вырезать и вставить из вывода git log):

$ git checkout --orphan newbranch oldbranch~N

затем сделайте новый коммит немедленно, как Ник Волынкин сказал (вы можете скопировать его сообщение):

$ git commit -C oldbranch~N

чтобы создать ветвь; а затем используйте git cherry-pick с oldbranch~N..oldbranch, чтобы получить остальные фиксации:

$ git cherry-pick oldbranch~N..oldbranch

(И, возможно, используйте -x, в зависимости от того, планируете ли вы лишить коммиты из oldbranch.) Помните, что oldbranch~N..oldbranch исключает сам commit oldbranch~N, но это действительно хорошо, потому что тот, который мы сделали как новый корневой фиксатор.

Ответ 3

Вот простой способ сделать это

git branch -m master old_master
git checkout --orphan master

-m = переместить ветвь в новое имя
checkout - проверка нового хозяина как сироты

Ответ 4

Ник Волынкин answer включает в себя создание хотя бы одного фиксации в новой сиротской ветке.
Это связано с тем, что git cherry-pick df931da..master без этой первой фиксации приведет к "Can't cherry-pick into empty head".

Но не больше, с git 2.9.X/2.10 (Q3 2016).

См. зафиксировать 0f974e2 (06 июня 2016 г.) Michael J Грубер (mjg).
(слияние Юнио С Хамано - gitster - в совершить 25227f0, 06 июля 2016 г.)

cherry-pick: разрешить выбор нерожденным ветвям

"git cherry-pick A" работал на неродившейся ветке, но "git cherry-pick A..B" не делал.

Это означает, что решение становится:

# make an orphan branch
➜  gitorphan git:(df931da) git checkout --orphan orphanbranch
Switched to a new branch 'orphanbranch'

# Now cherry-pick from previous branch a range of commits
➜  gitorphan git:(orphanbranch) git cherry-pick df931da..master

Нет необходимости совершать фиксацию сначала, прежде чем выбирать вишню.

Ответ 5

Скажем, у вас есть проверка нового ветки и сделано два коммита, как показано ниже. 13hh93 - контрольная сумма для проверки, а 54hdsf - контрольная сумма для последней фиксации:

master = > new_branch_1 (13hh93) = > new_branch_2 = > new_branch_3 (54hdsf)

Затем действуйте следующим образом. Шаг 1 переходит к началу проверки. Шаг 2 создает из него сиротскую ветку. Шаг 3 применяет остальную часть ветки к вашей сиротской ветке.

1) git выписка 13hh93 2) git checkout new_orphan_branch -orphan 3) git diff 13hh93 54hdsf | git применить

Ответ 6

Вишневый выбор для сиротской ветки работает до тех пор, пока не происходит коммитов. Слияния совершают это прерывание посередине.

error: Commit 732bf15efcb59c416eeade17eee9fcb8d50bc5ed - это слияние, но опция no -m не указана. фатальный: неудача вишневого захвата

Если вы добавите опцию -m в вишневый подборщик, вы получите:

ошибка: указана Mainline, но commit 0f45aa84b33b7a9bfeb41845c723dc51f82f1cf0 не является слиянием. фатальный: неудача вишневого захвата

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