TL; DR
Имена ветвей не работают так, как вы думаете.
Учитывая то, что вы хотите сделать, вы, вероятно, захотите рабочий процесс, ориентированный на ребазу, так как Никос C. прокомментировал . Помните, что rebase означает скопировать некоторые коммиты в новые и улучшенные коммиты, а затем переместить имя ветки, чтобы использовать новые и улучшенные коммиты, отказавшись от коммитов старых и паршивых. Это означает, что каждый , использующий перебазированную ветвь, должен быть готов отказаться от старых коммитов.
Long
Ветви или, точнее, ветка names , не иметь отношения родитель / ребенок. Имена ветвей - это просто метки, прикрепленные (или указывающие на), указывающие c коммитов .
коммитов , однако, do имеют родителя / детские отношения. В частности, последовательность команд, используемая для создания нового коммита, выглядит следующим образом:
git checkout somebranch
[edit various files]
git add file1.ext file2.ext
git commit
Шаг git checkout
означает выбор названной ветви и, таким образом, выберите коммит, который выберет имя ветви . Этот копирует содержимое этого коммита - которое в Git представляет собой полный снимок всех ваших файлов, хранящихся в специальном, только для чтения, Git -сжатом сжатом формате - в оба index aka область подготовки и рабочее дерево или рабочее дерево . 1
Шаг редактирования, конечно, работает с этими файлами в рабочем дереве.
Шаг git add
копирует обновленные файлы рабочего дерева - где вы внесли изменения - обратно в index / staging-area и последний шаг, git commit
, создает новый моментальный снимок с его замороженными на все время копиями файла из копий, находящихся в индексе. Любые файлы, которые вы не перезаписали с помощью git add
, остались такими же, как и в предыдущих git checkout
, поэтому новый снимок содержит все файлы, которые не были изменены и любые обновленные файлы, которые изменены соответствующим образом.
Новый коммит, однако, является child ранее помеченного коммита. Предыдущий коммит не изменился - ничто, даже сам Git не может изменить коммит после того, как он сделан, но теперь у него есть дочерний коммит. Только что созданный дочерний коммит записывает необработанный идентификатор ha sh его parent commit. Таким образом, связь идет только в одну сторону, от ребенка к родителю. Поскольку дочерний элемент является новым коммитом, он получает новый уникальный идентификатор ha sh.
Последний и важный шаг git commit
заключается в том, что записывает новый коммит ha sh ID. в название филиала . То есть метка перестает указывать на предыдущий родительский коммит и теперь указывает на дочерний коммит.
Это означает, что мы начинаем с цепочки коммитов, каждый с уникальным (и большим, и уродливым) га sh ID, который я заменим здесь одной заглавной буквой:
... <-F <-G <-H <-- develop
name develop
в настоящее время выбирает commit H
.
К этому вы добавляете новое имя, feature
, которое также выбирает коммит H
:
...--F--G--H <-- develop, feature
Теперь нам нужен способ запомнить , какое имя ветви вы Используешь . Для этого мы прикрепляем специальное имя HEAD
к одной ветви. Если вы git checkout develop
, мы добавим имя HEAD
к develop
:
...--F--G--H <-- develop (HEAD), feature
Если вы git checkout feature
, мы получим:
...--F--G--H <-- develop, feature (HEAD)
В любом случае, вы ' используйте commit H
, но вы делаете это через одно из двух имен, прикрепленных к / указывающих на коммит H
. (Тем временем commit H
указывает назад на коммит G
в качестве его родителя, а G
указывает на F
и т. Д.)
Теперь вы изменяете некоторые файлы в вашем рабочем дереве, git add
их, чтобы скопировать их обратно в область index / staging, и git commit
, чтобы сделать новый коммит I
. Вот что происходит, если вы используете feature
:
...--F--G--H <-- develop
\
I <-- feature (HEAD)
Имя HEAD
по-прежнему прикреплено к имени feature
, но feature
теперь указывает на новый коммит I
. Новый коммит I
указывает на коммит H
- где feature
указывал минуту назад - так что feature
содержит коммиты до и включая I
, а develop
содержит коммиты до и включая H
, Обратите внимание, что большинство коммитов по-прежнему находятся в обеих ветвях.
Если и когда вы добавляете коммиты к develop
, они просто добавляют к develop
:
...--F--G--H--K <-- develop (HEAD)
\
I--J <-- feature
Нет родителя / потомка связь между именами develop
и feature
. Это просто метки , идентифицирующие спецификацию c коммитов.
Так работают имена ветвей и тегов в Git: они идентифицируют коммиты. Различия между именем ветки и именем тега:
Имена ветвей перемещение во времени. На самом деле они двигаются автоматически! Имена тегов никогда не должны перемещаться. 2
Имена ветвей локальны для одного конкретного хранилища . Другие репозитории могут их видеть, но когда вы получаете коммиты от кого-то , ваш Git скопирует их ответвления , например master
, в ваши имена для удаленного слежения , как origin/master
. Имена тегов гораздо более глобальны: если они - кем бы они ни были - имеют v1.2
, и вы получаете от них ваши коммиты, ваш Git, вероятно, создаст ваш v1.2
для соответствия. 3
И, конечно же, имя ветви - это ответвление , а имя тега - это тег имя: есть несколько меньших дополнительные эффекты, но две вышеуказанные точки являются важными отличиями.
1 Дерево работы содержит фактические файлы в обычном обычном формате что вы и все другие не Git программы на вашем компьютере могут использовать. Эти фактические файлы не используются Git. Он использует сжатые, только для чтения, Git только зафиксированные файлы и использует индексную / промежуточную область для выполнения новых фиксаций, как показано выше.
2 Возможно перемещать тег, принудительно или удаляя его, а затем создавая его заново. Однако другое программное обеспечение может ожидать перемещения тегов , а не . Например, модульная система Go может кэшировать идентификатор ha sh тега, и если вы позже переместите тег, обновится , а не . Перемещать теги в целом - плохая идея: избегайте, если можете.
3 Это под вашим контролем, и правила по умолчанию здесь несколько сложны, но они означают, что вы получите их теги, если вы не сделали ничего особенного.
Использование rebase
Предположим, у вас есть это:
...--F--G--H--K <-- develop
\
I--J <-- feature (HEAD)
, но вы хотите это:
?--? <-- feature (HEAD)
/
...--F--G--H--K <-- develop
Вы не можете изменить существующие коммиты. Ничто не может. Существующие коммиты I
и J
застряли там, где они есть. Но вы можете копировать существующие коммиты в новые и улучшенные версии. В частности, вы можете скопировать коммит I
в новый улучшенный - мы назовем его I'
, чтобы указать, что это копия I
, хотя в Git он просто получает какую-то большую, некрасивую, случайную -haking sh ID, который не имеет никакого отношения к I
's ha sh ID. Разница между I
и I'
составит:
I
Родитель H
, но родитель I'
будет K
.
Скопировав I
в I'
, мы затем хотим получить Git копия J
в J'
. Результат выглядит следующим образом:
I'-J'
/
...--F--G--H--K <-- develop
\
I--J
Обратите внимание, что я стер ярлык feature
и полностью исключил имя HEAD
, так как на данный момент мы готовы двигаться feature
и сделать HEAD
присоединенным к новому, перемещенному feature
. Процесс, который выполняет эту «сборку новых коммитов», на самом деле использует то, что Git называет отсоединенным HEAD , поэтому сейчас фактическая ситуация такова:
I'-J' <-- HEAD
/
...--F--G--H--K <-- develop
\
I--J <-- feature
Обратите внимание, что специальное имя HEAD
не не привязан ни к какому имени ветви. Вот почему это отдельная ГОЛОВА . Между тем имя feature
по-прежнему выбирает существующий коммит J
.
Последнее действие команды git rebase
, чтобы завершить sh перебазирование, состоит в том, чтобы отделить имя feature
от существующего коммита (J
) и сделать так, чтобы он указывал на тот же коммит, на который HEAD
указывает (J'
):
I'-J' <-- feature, HEAD
/
...--F--G--H--K <-- develop
\
I--J [abandoned]
Переместив метку, rebase повторно присоединяет HEAD
к метке (перемещенной) , чтобы все выглядело так, как мы хотели. И так как Git находит коммитов, это начинать с меток и работать в обратном направлении, мы не можем увидеть коммитов I
и J
больше Все, что мы видим:
I'-J' <-- feature (HEAD)
/
...--F--G--H--K <-- develop
Если бы мы не помнили идентификаторы коммитов ha sh I
и J
, мы бы подумали, что Git каким-то образом переместил старые коммиты. Это не так - старые коммиты все еще там, и каждый, у кого есть клон хранилища и кто имеет старые коммиты, все еще имеет старые коммиты. Мы только видим наши новые , хотя.
Как Никос C. отметил, что если у вас есть активный запрос на получение, использующий существующие коммиты I-J
, вам, вероятно, потребуется использовать git push --force
для обновления запроса на получение. Вы должны получить все хранилища Git, в которых хранятся их копии коммитов I
и J
, чтобы отказаться от их в пользу новой и улучшенной цепочки I'-J'
. Точный способ убедить некоторых других Git перейти от старых коммитов к новым зависит от элементов управления, настроенных на , чем другие Git. GitHub позволяет простым git push --force
делать свое дело.