Git сбросить ветку от мастера, но сохранить коммит ветки - PullRequest
0 голосов
/ 10 февраля 2019

Я не уверен, задан ли этот вопрос, так как существует слишком много сценариев, касающихся github.

Итак, моя ситуация выглядит следующим образом:

  1. Я клонировал главную ветку в свой локальный рабочий каталог
  2. Я добавляю новые файлы в локальный рабочий каталог
  3. Я фиксирую изменения
  4. Затем я делаю git checkout -b new_branch
  5. Затем я помещаю локальный рабочий каталог в new_branch
  6. В основной ветке возникают проблемы с последнимирелиз.Необходимо выполнить откат к более старому выпуску.

Вопрос: Как я могу выполнить git reset --hard, чтобы все файлы в моем локальном каталоге были такими же, как и в предыдущей версии выпуска в главном, НО, сохраняя мою работу?например, новые созданные файлы.

Я попытался выполнить git reset до последнего выпуска SHA, но он сохранил файлы в моем локальном рабочем каталоге с последним выпуском.

Я попробовал git reset --hard, он удаляет все так же, как и мою работу.

Надеюсь, это объяснение достаточно ясно для вашей помощи.

Спасибо.

ОБНОВЛЕНИЕ

Более четкое понимание моего сценария: я клонирую мастер репо с тегом v2 на свой компьютер.Я создаю новую тестовую ветку.Я добавляю новые файлы и фиксирую, затем нажимаю на тестовую ветку.Теперь мой старший разработчик сказал мне, что у мастера с тегом v2 есть проблема, и попросил меня не использовать.Таким образом, моя тестовая ветвь нуждается в восстановлении с помощью тега v1.

Как я могу сделать это, не удаляя файлы, которые я зафиксировал, но удаляя файлы, созданные главным тегом v2?

1 Ответ

0 голосов
/ 11 февраля 2019

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' будет отказаться совершить IJ и 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', даже если это откажется от некоторых коммитов!

(Они все еще могут сказать «нет», но обычно, если это ваш ветвь, которую вы говорите им, чтобы заставить, они должны это позволить.)

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