TL; DR
Вы хотите git rebase
, за которым вы должны выполнить принудительный толчок.
Длинно, используя обновленный вопрос:
Я клонирую мастер репо с тегом v2 на свой компьютер [используя git clone <url>
].Я создаю новую тестовую ветвь.
Предположим, вы сделали это с:
git checkout -b test v2
, где v2
- рассматриваемый тег.В Git есть разница между именем ветви , например master
, и именем тега , как v2
.Разница на самом деле очень мала и состоит из двух тесно связанных между собой частей.
A имя ветви идентифицирует один конкретный коммит.Но , который фиксирует , меняется со временем.Имя идентифицирует последний или последний коммит, который мы хотели бы сказать, "на ветке".На самом деле, он изменит автоматически для вас, после того как вы запустите git checkout <branch-name>
и начнете делать новые коммиты.
A имя тега идентифицирует одинконкретный коммит.Он никогда не должен идентифицировать какой-либо другой коммит - он должен оставаться именем для , который коммит, навсегда.(Можно переместить метку - любой может сделать это, но в целях сохранения здравомыслия, просто не делайте этого.) И после git checkout <tag-name>
вы находитесь в немного странном состоянии, которое Git вызывает * 1047.* detached HEAD .
Причиной всего этого является то, что Git на самом деле вовсе не о ветвях , а скорее о коммитах.Коммит является своего рода фундаментальной единицей в Git.Каждый коммит имеет большой некрасивый хеш-идентификатор, уникальный для этого конкретного коммита.Эти хеш-идентификаторы, по сути, являются «настоящими именами» коммитов.Ни один коммит не может измениться - ни одного бита - потому что идентификатор хеша на самом деле является криптографической контрольной суммой содержимого коммита.Если бы вы могли как-то изменить содержимое, это изменило бы хеш-идентификатор, что означало бы, что у вас есть новый, другой коммит.
Эта криптографическая контрольная сумма также является , почему идентификаторы коммитов кажутся такимибессмысленно, и в значительной степени бесполезны для людей.Тот факт, что они бесполезны для людей, является причиной того, что нам нужны имена для них, и, следовательно, почему у нас есть имена ветвей и тегов.
Каждый коммит хранит несколько вещей, таких как имяи адрес электронной почты человека, который сделал фиксацию (и отметку времени), сообщение журнала для git log
, которое нужно показать, и полный и полный снимок каждого исходного файла.Одна из вещей, которую сохраняет каждый коммит, - это необработанный хэш-идентификатор его parent commit.Они образуют обратную цепочку:
... <-F <-G <-H
(где буквы обозначают фактические хэш-идентификаторы).
Учитывая хеш-идентификатор commit H
, Git может найтифактический коммит сам.Этот коммит содержит идентификатор хеша коммита G
, поэтому Git может найти G
.Найдя G
, Git получает хэш-идентификатор F
, так что он может найти F
и так далее.Так что Git постоянно работает в обратном направлении , начиная с последних и возвращаясь во времени.
Поскольку ничто внутри коммита не может измениться, мы можем просто нарисовать это в виде линейной последовательности:
...--F--G--H
Пока мы помним, что легко идти назад , но не идти вперед .
Создание нового коммита - это просто вопросиз:
Выбор имени ветви, который также выбирает последний коммит:
...--F--G--H <-- branch-name (HEAD)
При выборе ветви добавляется специальное имя HEAD
к названию, так что теперь HEAD
одновременно является синонимом как branch-name
, так и commit H
.
Выполнение некоторой работы и использование git add
для копирования файлов обратно в Git. index (также называемый промежуточной областью или иногда кеш , в зависимости от того, кто выполняет этот вызов).
Примечаниечто если вы используете git add
в существующих файлах, они будут скопированы в индекс поверх файлов, которые уже были в индексе, заменив некоторые старые версии.Если вы используете git add
в новых файлах, они были скопированы в индекс, но это просто новые файлы - они не перезаписывали ни одну предыдущую копию индекса.
Бег git commit
.Это замораживает копии индекса в подтвержденные версии.Он также собирает ваше лог-сообщение и делает вас автором и коммиттером этого нового коммита. родитель этого нового коммита является текущим коммитом H
:
...--F--G--H <-- branch-name (HEAD)
\
I
И теперь, когда новый коммит существует (и, следовательно, приобрел свой собственныйновый уникальный хэш-идентификатор), Git просто записывает свой хэш-идентификатор в имя ветви, так что branch-name
теперь указывает на фиксацию I
:
...--F--G--H
\
I <-- branch-name (HEAD)
Итак, если высделал git checkout -b test v2
, тогда у вас есть что-то подобное в вашем хранилище.Обратите внимание, что имя v1
идентифицирует некоторые другие (другие, возможно, более ранние) коммиты, поэтому давайте нарисуем их все:
...--F <-- tag: v1
\
G--H <-- tag: v2, test (HEAD)
Обратите внимание, что git checkout -b <em>name</em> <em>commit-ID</em>
устанавливает все так, чтобы новый name
указывает на выбранный коммит commit-ID
, затем обновляет его, заполняя индекс и рабочее дерево из этого коммита, и присоединяет специальное имя HEAD
к новому name
, все в одной команде.
Я добавляю новые файлы и фиксирую
ОК, поэтому вы создали новый файл в рабочем дереве (где вы делаете свою работу) и побежал git add newfile
.Это скопировало newfile
в ваш индекс - есть один индекс, который просто идет с этим одним рабочим деревом - и затем вы запустили git commit
, чтобы сделать новый коммит I
:
...--F <-- tag: v1
\
G--H <-- tag: v2
\
I <-- test (HEAD)
затем нажмите для проверки ветки.
Итак, на этом этапе вы запустили:
git push -u origin test
Это отправляет сам коммит I
другому Git по URL-адресу, под которым Git запоминаетимя origin
.Обратите внимание, что там есть целый отдельный репозиторий Git!Он имеет своих собственных ветвей и тегов, хотя из-за того, что теги никогда не перемещаются - или никогда не должны перемещаться - все ваши теги и их теги должны согласовываться относительно того, на какой фиксированный идентификатор хеша v1
и v2
указывают.
Отправив коммит I
, ваш Git затем попросил свой Git установить - или в этом случае create - свое собственное имя ветки test
, указывающее на коммит I
.Предположительно, все было в порядке с их Git на origin
, так что он это сделал.
Теперь мой старший разработчик сказал мне, что у мастера с тегом v2 есть проблема, и попросил меня не использовать.Таким образом, моя тестовая ветвь должна вернуться к мастеру с тегом v1.
Никакая часть любого существующего коммита не может когда-либо измениться.Это означает, что коммит I
застрял там, где он есть.Если вы сделали еще несколько коммитов, они все застряли:
...--F <-- tag: v1
\
G--H <-- tag: v2
\
I--J--K <-- test (HEAD)
Вам нужна новая серия коммитов, которые похожи на I-J-K
, но отличается в нескольких отношениях.В частности, вы хотите, чтобы ваши новые файлы были добавлены в снимок, который находится в F
, как указано тегом v1
.Затем Git должен зафиксировать этот снимок, повторно используя ваше сообщение о коммите из commit I
, чтобы сделать новые блестящие коммиты с новым и другим хеш-идентификатором, который мы назовем I'
, чтобы указать, что это копия I
:
I' <-- [somehow remembered as in-progress]
/
...--F <-- tag: v1
\
G--H <-- tag: v2
\
I--J--K <-- test
После успешного копирования I
в I'
вы теперь хотите, чтобы ваш Git скопировал J
в J'
таким же образом, а затем скопировал K
в K'
:
I'-J'-K' <-- [somehow remembered as in-progress]
/
...--F <-- tag: v1
\
G--H <-- tag: v2
\
I--J--K <-- test
Наконец, вы бы хотели, чтобы ваш Git очистил ваш test
ярлык от коммита K
и вместо этого вставил его в коммит K'
, а затем вернулся к вашей ветке test
как обычно, за исключением того, что теперь это означает коммит K'
:
I'-J'-K' <-- test (HEAD)
/
...--F <-- tag: v1
\
G--H <-- tag: v2
\
I--J--K [abandoned]
После того, как все это произошло, вам необходимо отправить новые коммиты I'-J'-K'
другому Git на origin
и сообщить origin
Gitчтобы переместить их test
, чтобы они указывали также на K'
.
git rebase
выполняет первую часть за вас
Сначала вы должны выполнить:
git checkout test
git status # and make sure everything is committed!
Если все выглядит хорошо, то вам просто нужно:
git rebase --onto v1 v2
Этот комmand сообщает Git: Скопируйте некоторые коммиты.Коммиты для копирования - это те, которые «включены» - технически, доступны из - моей текущей ветки, за исключением любых коммитов, которые также «включены» в имя v2
.Место для их размещения - после коммита, идентифицируемого по имени v1
.
- Имя
v2
имена коммитов H
, имена коммитов G
и т. Д.,Таким образом, эти коммиты не будут скопированы. - Текущее имя ветви,
test
, имена коммитов K
, какие имена J
, какие имена I
, какие имена H
, поэтому I-J-K
являются копируемыми. --onto v1
сообщает Git, куда помещать копии: после коммита F
, как указано v1
.
В конце процесса копирования Git возвращает ярлык test
(ваша текущая ветвь) с фиксации K
и вместо этого указывает на копию K'
.
git push
сейчастребует --force
Поскольку вы уже отправили коммит I
на origin
, , они имеют:
...--F <-- tag: v1
\
G--H <-- tag: v2
\
I <-- test
в качестве набора коммитов и их имена ветвей и тегов.Хотя, конечно, если вы отправили J
и K
с тех пор, их test
указывает на фиксацию K
.На данный момент мы можем просто предположить, что они указывают на I
и что они не имеют J
и K
, потому что, если они есть, а их test
указывает на их копию K
Обратите внимание, что хэш-идентификаторы и сами базовые коммиты одинаковы в в каждом репозитории Git.Вот почему хеш-идентификаторы являются криптографическими контрольными суммами содержимого!Пока у вас и у них одинаковое содержимое, у вас одинаковый коммит, и поэтому он имеет одинаковый хэш-идентификатор.Если у вас разные хеш-идентификаторы, у вас разное содержимое и разные коммиты.Все, что нам нужно знать: есть ли у вас этот хэш-идентификатор?
Если вы не злоупотребляли именами тегов, они также одинаковы во всех репозиториях Git: одни и те же имена идентифицируют одинаковые коммиты.Но имена веток специально различаются, потому что Git создан для добавления новых коммитов.
Так что теперь вы запустили git rebase
и отказались от трех старых коммитов I-J-K
, теперь вы снова запустите git push origin test
.Это отправит им I'-J'-K'
- ваши новые коммиты, которые у вас есть, которых у них нет, что ваш Git и их Git могут сказать только по хеш-идентификатору - и затем попросят их перенести свой тест из любого местатеперь он указывает в их хранилище - на I
или, возможно, на K
.Вы просите их переместить его так, чтобы он указывал на K'
.
Они скажут нет .Причина, по которой они скажут «нет», заключается в том, что их Git увидят, что перемещение их test
из I
или K
, в зависимости от того, какое значение установлено сейчас, на K'
будет отказаться совершить I
(и J
и K if they have those). So instead of politely asking the other Git, the one at
источник , to please if it's OK update
проверить to point to
K'`, вам нужно сделать:
git push --force origin test
, чтобы сказать им: Переместите test
, чтобы указать K'
, даже если это откажется от некоторых коммитов!
(Они все еще могут сказать «нет», но обычно, если это ваш ветвь, которую вы говорите им, чтобы заставить, они должны это позволить.)