Как изменить начальную точку ветви?

обычно я создаю ветку, выполнив команду git checkout -b [branch-name] [starting-branch]. В одном случае я забыл включить starting-branch и теперь я хочу исправить это. Как это сделать после того, как филиал уже создан?

3 ответов


короткий ответ заключается в том, что как только у вас есть некоторые коммиты, вы хотите git rebase их, используя длинную форму git rebase: git rebase --onto newbase upstream. Чтобы узнать, как определить каждый из них, см. (Очень) длинный ответ ниже. (К сожалению, это немного вышло из-под контроля, и у меня нет времени, чтобы сократить его.)

проблема у вас здесь в том, что в Git ветви не есть "отправная точка" - по крайней мере, не в каком-либо полезном смысле.

термин "филиал", в Git, неоднозначно

первая проблема здесь заключается в том, что в Git слово "ветвь" имеет по крайней мере два разных значения. Обычно, когда мы говорим свободно о "ветви", из контекста ясно, имеем ли мы в виду ветвь имя-вещь, которая является словом, как master или develop или 1 см. также что именно мы подразумеваем под "ветка"?

в данном конкретном случае, к сожалению, вы имеете в виду и то и другое одновременно.


1термин DAG является коротким для направленного ациклического графа, который является графом фиксации: набор вершин или узлов и направленных (от дочернего к родительскому) ребер, так что нет циклов через направленные ребра от любого узла обратно к себе. К этому я просто добавляю "- пусть " уменьшительный суффикс. Этот получившееся слово имеет счастливое сходство со словом аксельбант, плюс определенный ассонанс со словом "Кинжал", что делает его немного опасным: "это DAGlet, который я вижу передо мной?"

нарисуйте график фиксации

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

...--o--o--o           <-- master
         \
          o--o--o--o   <-- develop

круглые o узлы представляют commits, и названия отделения master и develop укажите на один конкретный совет commit на каждой ветви.

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

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

...--A--B--C           <-- master
         \
          D--E--F--G   <-- develop

и теперь легко сказать, например, что имя master указывает на совершение C и C указывает на B и B указывает на A, что указывает на большую историю, о которой мы действительно не заботимся, и, следовательно, просто оставили как ....

где находится филиал начать?

теперь, это совершенно очевидно, для вас и меня, основываясь на этом графике, эта ветвь develop, чей кончик commit G, начинается с commit D. Но это не очевидно для Git-и если мы нарисуем один и тот же график немного по-другому, это может быть менее очевидно для вас и меня тоже. Например, посмотрите на этот рисунок:

          o             <-- X
         /
...--o--o--o--o--o--o   <-- Y

очевидно филиала X имеет только одну фиксацию, а основная строка -Y, да? Но давайте напишем несколько писем. в:

          C             <-- X
         /
...--A--B--D--E--F--G   <-- Y

и затем перейти Y вниз строку:

          C            <-- X
         /
...--A--B
         \
          D--E--F--G   <-- Y

и посмотрите, что произойдет, если мы двинемся C вниз к главной линии, и поймите, что X is master и Y is develop? В каком отделении is фиксация B в конце концов?

в Git коммиты могут быть на много ветви одновременно; DAGlets до вас

ответ Git на эту дилемму заключается в том, что совершает A и B на и филиалы. Начало ветви X далеко слева, в ... часть. но таково начало ветви Y. что касается Git, ветвь "начинается" с любой корневой фиксации(ов), которую она может найти в графике.

это важно иметь в виду в целом. Git не имеет реального понятия о том, где ветка "началась", поэтому нам приходится давать ей дополнительные информация. Иногда эта информация подразумевается, а иногда она является явной. Также ВАЖНО, В общем, помнить, что коммиты часто находятся на много ветви - поэтому вместо указания ветвей мы обычно указываем коммиты.

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

в вашей дело, если вы напишете имя develop и попросите Git выбрать эту фиксацию и его предки, вы получаете совершает D-E-F-G (который вы хотели) и фиксация B, и фиксация A и так далее (что вы не сделали). фокус в том, чтобы как-то определить, что вас связывает не хотите, вместе с которым совершаете.

обычно мы используем две точки X..Y синтаксис

с большинством команд Git, когда мы хотим выбрать какой-то конкретный DAGlet, мы используем синтаксис с двумя точками, описанный в gitrevisions, например master..develop. Большинство2 команды Git, которые работают с несколькими коммитами, рассматривают это как: "Выберите все коммиты, начиная с кончика develop ветвь, но затем вычесть из этого набора набор всех коммитов, начиная с кончика master филиала.- Оглянитесь на наш график master и develop: здесь говорится: "принимайте коммиты, начиная с G и работает в обратном направлении" - который получает нас слишком много, так как он включает коммиты B и A и раньше-"а исключить коммиты, начиная от C и работает в обратном направлении."Это исключить часть, которая дает нам то, чего мы хотим.

следовательно, писать master..develop так мы называем коммиты D-E-F-G, и имейте git вычислить это автоматически для нас, без необходимости сначала сядьте и нарисуйте большой кусок графика.


2два заметных исключения -git rebase, который находится в его собственном разделе, чуть ниже, и git diff. The git diff команда лечит X..Y как просто смысла X Y, т. е. он фактически просто полностью игнорирует две точки. Обратите внимание, что это имеет совсем другой эффект, чем вычитание набора: в нашем случае git diff master..develop просто diffs дерево для фиксации C против дерева для фиксации G, хотя master..develop никогда не совершал C в первом сете.

другими словами, математически говоря, master..develop это нормально ancestors(develop) - ancestors(master), где ancestors функция включает указанную фиксацию, т. е. тестирует ≤, а не просто<.>ancestors(develop) не включает commit C на всех. Операция вычитания набора просто игнорирует наличие C в наборе ancestors(master). Но когда вы кормите это git diff, это не игнорировать C: это не отличается, скажем,B против G. Хотя это было бы разумно,git diff вместо этого крадет триточка master...develop синтаксис для этого.

в Git rebase немного специального

на rebase команда почти всегда используется для перемещения3 один из этих подмножеств фиксации DAGlet из одной точки графика в другую. На самом деле, это то, что rebase, или было изначально в любом случае, определение. (Теперь у него есть фантазия интерактивные rebase режим, который делает это и кучу больше операций редактирования истории. Mercurial имеет аналогичную команду,hg histedit, с немного лучшим именем и гораздо более жесткой семантикой по умолчанию.4)

так как мы всегда (или почти всегда) хотите, чтобы переместить DAGlet, git rebase строит в этом выборе подмножества для нас. И, поскольку мы всегда (или почти всегда) хотим переместить Кинжал, чтобы прийти сразу после кончика некоторые другое филиала, git rebase по умолчанию выбирается цель (или --onto) фиксация с использованием имени ветви, а затем использует то же самое имя ветви в X..Y синтаксис.5


3технически git rebase на самом деле копии совершает, а не двигать их. Он должен, потому что коммиты неизменяемые, как и все внутренние объекты Git. Истинное имя, хэш SHA-1, фиксации - это контрольная сумма битов, составляющих фиксацию, поэтому каждый раз, когда вы что-то меняете, включая что-то простое, как родительский идентификатор, вы должны сделать новая, слегка-разные, совершают.

4в Mercurial, совсем не похоже на Git, ветви действительно do есть отправные точки, и-что более важно для histedit-совершает записи этап: секретно, черновик или опубликовано. Редактирование истории легко применяется к secret или draft-phase коммиты, и не столько опубликованные коммиты. Это верно и для Git, но поскольку Git не имеет понятия о фазах фиксации, ребаза Git должна использовать эти другие методы.

5технически <upstream> и --onto аргументы могут быть только сырые фиксации идентификаторов. Обратите внимание, что 1234567..develop отлично работает как селектор диапазона, и вы можете перебазировать --onto 1234567 для размещения новых коммитов после фиксации 1234567. Единственное место, которое git rebase действительно нужна ветка имя для имени текущей ветви, которую он обычно просто читает из HEAD в любом случае. Однако, мы обычно хочу использовать имя, вот как я описываю все это здесь.


то есть, если мы сейчас на ветке develop, и в этой ситуации, которую мы рисовали раньше:

...--A--B--C           <-- master
         \
          D--E--F--G   <-- develop

мы, вероятно, просто хотим переместить D-E-F-G цепь на кончике master для этого:

...--A--B--C              <-- master
            \
             D'-E'-F'-G'  <-- develop

(The причина, по которой я изменил имена с D-E-F-G to D'-E'-F'-G' это то, что rebase вынужден скопировать оригинал совершает, а не фактически перемещает их. Новые копии так же хороши, как и оригиналы, и мы можем использовать одно и то же имя из одной буквы, но мы должны по крайней мере отметить, хотя бы смутно, что это на самом деле копии. Это то, что отмечает" prime",' символы, для.)

потому что это то, чего мы обычно хотим,git rebase сделает это автоматически, если мы просто назовем другое филиала. То есть, мы на develop теперь:

$ git checkout develop

и мы хотим перебазировать коммиты, которые находятся на ветке develop и не на master, перемещая их на кончик master. Мы могли бы выразить это как git somecmd master..develop master, но тогда нам придется ввести слово master дважды (такая ужасная судьба). Поэтому вместо того, в Git rebase выводит это когда мы просто тип в:

$ git rebase master

имя master становится левой стороной двух точек .. daglet селектор, и имя master и становится целью rebase; и Git затем rebases D-E-F-G на C. Git получает наш филиал имя, develop, прочитав текущее название ветви. Фактически, он использует ярлык, который заключается в том, что когда вам нужно текущее имя ветви, вы обычно можете просто написать HEAD вместо. Так что master..develop и master..HEAD означает то же самое, потому что HEAD is develop.

в Git!--97--> называет это имя <upstream>. то есть, когда мы говорим git rebase master, git утверждает, в документации, что master - это до git rebase. Затем команда rebase работает с коммитами в <upstream>..HEAD, копируя их после любой фиксации в <upstream>.

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

(Rebase также имеет скрытую, но желательную, боковую особенность опустить любой из D-E-F-G совершает, что достаточно напоминает commit C. Для наших целей мы можем игнорировать это.)

что не так с другим ответом на этот вопрос

в случае, если другой ответ удаляется или становится одним из нескольких других ответов, я обобщу его здесь как "использовать git branch -f для перемещения метки ветви.- Изъян в другом ответе, а может быть, и больше. главное, именно , когда это проблема-становится очевидным, как только мы рисуем наши графические Даглеты.

имена ветвей уникальны, но коммиты наконечника не обязательно так

давайте посмотрим, что происходит, когда вы запускаете git checkout -b newbranch starting-point. Это просит Git укорениться в текущем графике для данной начальной точки и сделать новую метку ветви точкой этой конкретной фиксации. (Я знаю, что сказал выше, что ветви не есть начальный точка. Это все еще в основном верно: мы даем точка отсчета теперь, но Git собирается установить его, а затем, что важно,забыть его.) Допустим, что starting-point это еще одно название ветви, и давайте нарисуем целую кучу ветвей:

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- brC
                 \
                  o--o   <-- brD

так как у нас четыре филиала имена у нас есть четыре филиала советы: четыре коммита ветвей, идентифицированные по именам brA через brD. Мы выбираем один и делаем новое название филиала newbranch к же совершить как один из этих четырех. Я произвольно выбрал brA здесь:

          o--o--o--o     <-- brA, newbranch
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- brC
                 \
                  o--o   <-- brD

теперь у нас есть пять имен и пять ... четыре? ... ну, некоторые совет совершает. Хитрость в том, что brA и newbranch и выберите пункт тот же совет совершал.

ГИТ знает-потому что git checkout устанавливает его - что мы теперь на newbranch. В частности, Git пишет имя newbranch на HEAD. Мы можем сделать наш рисунок более точным, добавив эту информацию:

          o--o--o--o     <-- brA, HEAD -> newbranch
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- brC
                 \
                  o--o   <-- brD

на данный момент четыре коммита, которые раньше были только на ветке brA теперь на обоих brA и newbranch. И, по той же причине, Git больше не знает, что newbranch начинается на кончике brA. Что касается Git, оба brA и newbranch содержат эти четыре коммита и все предыдущие тоже, и оба они "начинают" где-то в далеком прошлом.

когда мы делаем новые коммиты, то нынешнее название движется

так как мы на ветке newbranch, если мы сделаем новую фиксацию сейчас, родителем новой фиксации будет старая фиксация подсказки, и Git отрегулирует имя ветви newbranch, чтобы указать на новый коммит:

                     o   <-- HEAD -> newbranch
                    /
          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- brC
                 \
                  o--o   <-- brD

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

обратите внимание, что Git по-прежнему понятия не имеет, что ветка newbranch "начал" в brA. Это просто случай, теперь, что newbranch содержит один фиксатор, который brA нет, плюс четыре коммита, которые они оба содержат, плюс все предыдущие коммиты.

что git branch -f does

используя git branch -f позволяет переместить метку ветви. Допустим, по каким таинственная причина, мы не хотим ярлык ветви brB чтобы указать, где он делает в нашем текущем чертеже. Вместо этого мы хотим, чтобы он указывал на ту же фиксацию, что и brC. Мы можем использовать git branch -f to изменить место, на которое brB точки, т. е. для перемещения метки:

$ git branch -f brB brC

                     o   <-- HEAD -> newbranch
                    /
          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    [abandoned]
            \
             o--o--o     <-- brC, brB
                 \
                  o--o   <-- brD

это заставляет Git " забыть "или" отказаться " от тех трех коммитов, которые были только на brB раньше. Это, вероятно, плохая идея-почему сделал мы решили сделать этот странный вещь?- так мы, наверное, хотим поставить brB обратно.

Reflogs

к счастью," заброшенные " коммиты обычно запоминаются в том, что Git называет reflogs. Reflogs использует расширенный синтаксис,name@{selector}. The селектор часть обычно является числом или датой, например brB@{1} или brB@{yesterday}. Каждый раз, когда Git обновляет имя ветви, чтобы указать на некоторую фиксацию, он записывает запись reflog для этой ветви с идентификатором фиксации, a отметка времени и необязательное сообщение. Запустить git reflog brB чтобы увидеть эти. The git branch -f команда написала новую цель как brB@{0}, натыкаясь на все старые номера, так что теперь brB@{1} имена совет совершал. Итак:

$ git branch -f brB 'brB@{1}'
    # you may not need the quotes, 'brB@{...}' --
    # I need them in my shell, otherwise the shell
    # eats the braces.  Some shells do, some don't.

вернет его (а также снова перенумерует все номера: каждое обновление заменяет старое @{0} и делает это @{1} и @{1} становится @{2} и так далее).

во всяком случае, предположим, что мы делаем наши git checkout -b newbranch пока мы на brC, и не упомянуть brA. То есть Начнем с:

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- HEAD -> brC
                 \
                  o--o   <-- brD

и работать git checkout -b newbranch. Тогда мы получаем следующее:

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- brC, HEAD -> newbranch
                 \
                  o--o   <-- brD

если мы означает сделать newbranch момент совершения brA, мы можем сделать это прямо сейчас, с git branch -f. Но предположим, мы делаем новое обязательство, прежде чем осознаем, что мы сделали newbranch начните с неправильной точки. Давайте нарисуем:

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            \
             o--o--o     <-- brC
                 \  \
                 |   o   <-- HEAD -> newbranch
                 \
                  o--o   <-- brD

если мы используем тег git branch -f теперь, мы оставим-потеряем-обязательство, которое только что приняли. Вместо этого мы хотим перебазировать его на фиксацию этой ветви brA пункта-до.

простой git rebase слишком много копий

что делать, если вместо использования git branch -f, мы используем git rebase brA? Давайте проанализируем это с помощью-что еще-наш DAGlets. Мы начинаем с приведенного выше рисунка выше, с вытянутой ноги, выходящей на brD, хотя в конце концов мы можем игнорировать эту ногу, и с разделом собирается brB большинство которые мы также можем игнорировать. Что мы не можем игнорировать, так это то, что мы получаем, прослеживая линии назад.

на git rebase команда, в этой форме, будет использовать brA..newbranch чтобы выбрать коммиты для копирования. Так, начиная с DAGlet, обозначим (с *) все коммиты, которые находятся на (или) newbranch:

          o--o--o--o     <-- brA
         /
...--*--*--*--o--o--o    <-- brB
            \
             *--*--*     <-- brC
                 \  \
                 |   *   <-- HEAD -> newbranch
                 \
                  o--o   <-- brD

теперь, давайте un-mark (с x) все коммиты, которые включены (или содержатся в) brA:

          x--x--x--x     <-- brA
         /
...--x--x--*--o--o--o    <-- brB
            \
             *--*--*     <-- brC
                 \  \
                 |   *   <-- HEAD -> newbranch
                 \
                  o--o   <-- brD

что останется-все * commits-это те, которые git rebase копировать. это слишком много!

нам нужно добраться git rebase для копирования только одного коммита. Это означает, что для


Если у вас нет коммитов в новой ветке, использование "git reset --hard" проще.

Если у вас есть коммиты в новой ветке...

Если ваша ветка начинается с более старой фиксации, которую вы хотите, просто сделайте "git rebase".

Если, что более маловероятно, ваша ветвь начинается с более новой фиксации или на совершенно другой ветви, используйте "git rebase --on"


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

git branch -f <branch-name> <starting-branch>

обратите внимание, что если branch-name является текущей ветвью, вы должны сначала переключиться на другую ветвь, например, с git checkout master.