Как я могу git перебазировать один коммит на мастер и pu sh как новую ветку - PullRequest
0 голосов
/ 03 апреля 2020

Я сделал неправильный коммит commit_c на ветке branch_a.
Как мне git перебазировать один коммит (commit_c) на мастер и pu sh в качестве новой ветки branch_b.
Так что у меня такой же сценарий, как если бы я создал ветку из master, сделал те же изменения, что и в commit_c и нажал branch_b.
Примечание: я хочу отменить коммит, я не хочу черри, так как это создаст новый commit_d верно?

1 Ответ

0 голосов
/ 03 апреля 2020

Без коммита, после его создания, когда-либо может быть изменено. Таким образом, вы должны сделать новый коммит независимо от всего остального.

Важно понимать это о Git: коммиты имеют значение, а коммиты - это навсегда. У вас есть коммит, который вам не нравится. Вам понадобится сделать новый коммит - как бы "новый и улучшенный", чтобы использовать вместо него.

Еще одна вещь, которую нужно знать о коммитах, это то, что каждый коммит имеет уникальный ха sh ID. Этот идентификатор ha sh теперь (и навсегда) зарезервирован для , который commit. Каждый Git репозиторий везде будет использовать этот га sh ID для , который коммит, если репозиторий имеет этот коммит. Если в репозитории нет этого коммита, у него не будет ничего с таким ha sh ID. 1

Однако, то, что коммиты - это навсегда, немного преувеличивает. Эти га sh идентификаторы большие, и уродливые, и для людей невозможно получить права и запомнить. Так что мы не используем их. Git использует их внутренне. Мы используем имена . Имя - имя ветви, например master, или имя тега, например v2.1, - содержит идентификатор ha sh. Это большая часть того, что делает имя: держите идентификатор ha sh. Это позволяет нам - или Git - найти один конкретный коммит по имени.


1 Технически, пока два Gits никогда не будут "встретиться", это нормально для них, чтобы иметь разные объекты, которые имеют одинаковый идентификатор ha sh. Если они когда-нибудь встретятся, они подумают, что у них уже есть объекты друг друга, и если вы пытаетесь заставить их иметь пол репозитория, чтобы они приобретали объекты друг друга, это будет неправильно. На практике такого рода дублирование никогда не происходит. Теоретически можно сделать , чтобы это произошло нарочно, но посмотрите Как только что обнаруженное столкновение SHA-1 влияет на Git?


Что хорошего найти один коммит делать?

Каждый коммит содержит полный снимок всех ваших файлов. Коммиты - это не изменения , это снимки. Этот снимок обычно составляет основную часть любого коммита: его данные. Но каждый коммит также имеет некоторые метаданные или информацию о данных. Это включает в себя такие вещи, как, кто сделал коммит, когда и почему (сообщение журнала). Однако в метаданных есть еще один ключевой элемент - или список. Каждый коммит содержит необработанный идентификатор ha sh своего немедленного - предыдущего или parent commit. (Коммиты слияния имеют двух или более родителей, так что это список идентификаторов ha sh, а не один идентификатор ha sh.)

Это означает, что если мы сможем найти последний коммит в цепочке, мы можем найти всю цепочку:

... <-F <-G <-H   <--master

name master содержит га sh ID last совершать. В этом случае master содержит идентификатор ha sh некоторого коммита с большим уродливым ха sh, который мы просто собираемся назвать H. Мы говорим, что имя master указывает на H.

Между тем сам фактический коммит, полученный Git из его большой базы данных all-commitits-and- other-internal-objects, содержит идентификатор ha sh некоторого ранее commit. Мы назовем его G, а не угадываем какой-либо идентификатор ha sh, поэтому мы говорим, что H указывает на G.

Commit G является тоже обычный коммит, поэтому он указывает - содержит идентификатор ha sh - своего родителя F. F конечно, также указывает на некоторый более ранний коммит, который продолжает указывать назад. Процесс останавливается только тогда, когда мы достигаем самого первого в истории коммита, который - поскольку он не может указывать назад - вообще не имеет родителя .

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

Нарисуйте свои коммиты

Всякий раз, когда у вас есть серия коммитов и некоторые имена веток, Нарисуй их . Приступайте к практике, делая это на доске, на бумаге или чем-то еще, потому что это понадобится вам как общий навык при работе с Git. (Вы можете Git сделать это за вас, используя git log --graph - что dr aws довольно грубый ASCII-графический график - или сделать это с графическим интерфейсом, но это хорошая идея сделать это несколько раз самостоятельно тоже.)

Давайте посмотрим добавление одного коммита на master в крошечном хранилище всего с тремя коммитами. Мы начинаем с:

A <-B <-C   <--master

We git checkout master, который выбирает коммит C в качестве текущего коммита . Это извлекает все его замороженные навсегда файлы в рабочую область (наше рабочее дерево или рабочее дерево ), а также в Git ' index (из которого Git делает новые коммиты; мы не будем go вдаваться в детали, но важно знать об индексе).

Теперь мы внесем некоторые изменения в рабочее дерево файлы, используйте git add, чтобы скопировать их обратно в индекс, и запустите git commit, чтобы сделать новый снимок. Этот новый снимок получит большой уродливый идентификатор sh, но мы сделаем все просто и назовем его D. D необходимо указать на C. Поскольку мои таланты грубого ASCII-графического рисования ограничены, я нарисую его так:

A--B--C   <-- master
       \
        D

Теперь name master must указывает на последний коммит, поэтому Git теперь записывает D ха sh ID в имя master:

A--B--C
       \
        D   <-- master

Нарисуйте имена ваших ветвей

Давайте немного перемотаем на:

A--B--C   <-- master

Если у вас более одного имени ветви, вы можете нарисовать дополнительные имена. Давайте создадим новую ветку dev для разработки. Имя должно указывать на коммит. Какой коммит мы должны использовать? Давайте воспользуемся последним, C:

A--B--C   <-- master, dev

Обратите внимание, что все три коммита теперь находятся на обеих ветвях. Последний master коммит - C, а последний dev коммит - также C.

Нам нужно знать, какое имя ветви мы используем. В любом случае, сейчас мы будем использовать commit C, но чтобы вспомнить, какое имя мы используем, давайте добавим имя HEAD. Вы можете нарисовать это как другой указатель:

                      HEAD
                       |
                       v
A--B--C   <-- master, dev

или использовать меньше места следующим образом:

A--B--C   <-- master, dev (HEAD)

По сути, мы прикрепляем специальное имя HEAD к одному из имен ветвей. Имя, к которому присоединено HEAD - это ветвь, которую мы использовали с нашим git checkout.

Давайте сделаем коммит D сейчас, на dev:

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

Итак * Git делает, когда мы делаем новый коммит, чтобы заставить новую точку коммита вернуться к текущему коммиту - как нашло текущее имя ветки, как найдено HEAD - и затем записать ха sh нового коммита. Идентификатор в текущем имени ветви, найденный по HEAD.

Если мы go вернемся к master, с git checkout master, мы получим это:

A--B--C   <-- master (HEAD)
       \
        D   <-- dev

Нет коммиты вообще изменились, но теперь мы снова используем commit C. Если мы делаем новый коммит E, нам нужно нарисовать его либо на новой строке:

        E   <-- master (HEAD)
       /
A--B--C
       \
        D   <-- dev

, либо на той же строке:

A--B--C--E   <-- master (HEAD)
       \
        D   <-- dev

Оба чертежа представляют одно и то же: в обоих случаях коммиты A-B-C находятся на в обеих ветвях . Фиксация D только на dev, а фиксация E только на master.

Каким образом вы должны их рисовать? Ну как угодно. Вы можете поместить новые коммиты вверх или вниз, а не вправо. Просто помните, что каждый коммит указывает назад , а каждое имя ветви указывает на последний коммит. Git вызывает последний коммит tip commit.

Это нормальный способ работы веток в Git: мы добавляем к ним новые коммиты . Коммиты, которые были на ветке, все еще там, на ветке; Новый коммит - это новый совет ветви.

Забыв коммит

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

Мы можем Git переместить имя ветви. Когда мы делаем это, мы можем переместить его назад от нормального направления. То есть предположим, что у нас есть:

        E   <-- master (HEAD)
       /
A--B--C
       \
        D   <-- dev

и мы говорим Git: переместить имя master на один шаг назад к C. Результат:

        E
       /
A--B--C   <-- master (HEAD)
       \
        D   <-- dev

Фиксация E все еще существует, но мы не можем ее найти. Она была заброшена! Есть способ найти A, начиная с C и возвращаясь к B и затем A, или начиная с D и возвращаясь назад, но коммиты только позволяют вам go назад, а не вперед, так что вы не можете получить от C до E. В конце концов, Git увидит, что мы не можем его найти и не использовали его долгое время (по крайней мере, 30 дней, по умолчанию), и в конце концов удалит его.

Следовательно, мы можем получить наши Git до забывают этот плохой коммит E, который мы сделали. Он будет задерживаться на некоторое время, но в конечном итоге он исчезнет go. Это предполагает, что мы, конечно, не скопировали commit E в какой-то другой Git репозиторий. Если мы использовали git push для отправки E некоторым другим Git, то другой Git теперь имеет копию E и имеет наш Git забудь наши ничего не сделают с их .

Вернемся к твоей проблеме

Теперь, когда у нас есть эта структура, мы можем Нарисуйте вашу проблему и ясно посмотрите, что мы должны делать:

          I--J   <-- branch-a (HEAD)
         /
...--G--H   <-- master

Допустим, это коммит J, это неправильно. Мы бы хотели, чтобы другой коммит K пришел от H, и чтобы имя branch-b указывало на J. Вот как мы можем сделать это с git cherry-pick, который копирует коммит в новый (и, возможно, улучшенный) коммит. Сначала мы git checkout master выберем коммит H:

          I--J   <-- branch-a
         /
...--G--H   <-- master (HEAD)

Затем мы создадим новое имя ветви branch-b здесь и git checkout branch-b:

          I--J   <-- branch-a
         /
...--G--H   <-- master, branch-b (HEAD)

Тогда мы запустим git cherry-pick branch-a. имя branch-a выбирает коммит J, так что это коммит, который Git скопирует:

          I--J   <-- branch-a
         /
...--G--H   <-- master
         \
          K   <-- branch-b (HEAD)

Если мы хотим, чтобы Git забыл коммит J, мы можем заставить имя branch-a указать на I сейчас. Есть несколько способов сделать это - я буду использовать короткий через мгновение - но мы хотим:

            J   [abandoned]
           /
          I   <-- branch-a
         /
...--G--H   <-- master
         \
          K   <-- branch-b (HEAD)

Очень короткая последовательность Git команд для go из начального графика, к этому, это:

git checkout -b branch-b master
git cherry-pick branch-a
git branch -f branch-a branch-a~1   # a little bit magic

Но: что если это коммит I, который мы хотим скопировать в K? Ну, мы можем начать с того же git checkout -b branch-b master, чтобы получить:

          I--J   <-- branch-a
         /
...--G--H   <-- master, branch-b (HEAD)

Теперь мы запустим git cherry-pick branch-a~1. Этот суффикс ~1 означает обратный отсчет одного коммита , т. Е. Начните с J, где branch-a указывает, а затем вернитесь на один шаг назад для коммита I. Итак, теперь у нас будет:

          I--J   <-- branch-a
         /
...--G--H   <-- master
         \
          K   <-- branch-b (HEAD)

, где K - копия I.

Больше альтернатив

Фактически, начиная с I и K оба имеют одного и того же родителя и одного и того же снимка , мы могли бы сделать это вместо этого, что может быть умнее:

            J   <-- branch-a
           /
          I   <-- branch-b
         /
...--G--H   <-- master

Вам необходимо определить какой результат вы хотите

Реальный вопрос здесь в том, что вы хотите сделать с коммитом J. Что бы мы ни делали, коммит J все еще имеет I в качестве родителя. Это буквально невозможно изменить: ничего о любом коммите нельзя когда-либо изменить.

Предположим, результат, который вы хотите , в конце концов, выглядит больше похоже на это:

        I--J   [abandoned]
        |
        | L   <-- branch-a
        |/
...--G--H   <-- master
         \
          K   <-- branch-b

В этом случае вам необходимо скопировать I в K и J в L. Но, возможно, вы можете использовать вместо этого:

          L   <-- branch-a
         /
...--G--H   <-- master
         \
          I   <-- branch-b
           \
            J   [abandoned]

Ключ всегда:

  • Нарисуйте, что у вас есть.
  • Подумайте, что вы хотите вместо этого (и, возможно, нарисовать это тоже).
  • Выбрать Git команды, которые делают то, что вы хотите.

История в вашем хранилище Git состоит из всех коммиты, которые вы можете найти, начиная с имен веток (и других имен), чтобы найти один указанный c коммит, и работая в обратном направлении оттуда.

имена филиалов в вашем Git хранилище находятся под вашим контролем. Вы можете делать с ними все, что захотите, в любое время. Вы можете переименовать их, переместить или удалить их. Вы можете сделать новые. Каждое из ваших имен ветвей находит один tip commit , и оттуда Git будет работать в обратном направлении.

Вы действительно застряли с коммитом только после того, как скопируете его в другое Git хранилище . Этот другой Git репозиторий имеет свои собственные имена ветвей, и если он сможет найти фиксацию, которая будет иметь тот же идентификатор ha sh, как ID sh никогда не могут измениться, то он будет продолжать находить этот коммит. Если вы позволите плохому коммиту выйти, вы должны получить каждый Git репозиторий, в котором он есть, чтобы отказаться от него. Обычно гораздо проще просто оставить его там, плохо, и добавить новый коммит, чтобы исправить это, потому что репозитории Git жадны до новых коммитов: дайте им новый коммит, и они обычно его возьмут. Попробуйте убрать коммит, и вам нужно приложить много силы (git branch --force или git reset).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...