Разница между созданием ветки и мягким сбросом? Лучший способ вернуться к старой рабочей версии? - PullRequest
1 голос
/ 24 марта 2019

Скажите, что история моих коммитов A - B - C, и у меня есть только эта ветвь.

Б полностью работал. Я начал добавлять некоторые функции в C, но он не работает, поэтому Мне нужно вернуться к B, но я также хочу сохранить код, написанный на C , потому что я захочу просмотреть его и исправить позже. , Каков наилучший способ сделать это?

Лучший способ создать новую ветку, начиная с B?

В чем разница между этим и мягким сбросом? Я понимаю, что программный сброс не удаляет изменения (это правильно?), Но мне не ясно, как восстановить эти изменения (код на C), и в чем разница между программным сбросом и созданием ветви.

Помимо

Гит просто кажется излишне загадочным и неясным. Я имею в виду, официальные документы определяют толчок как:

https://git -scm.com / Docs / ГИТ-толчок

git-push - Обновить удаленные ссылки вместе со связанными объектами

Я уверен, что это технически правильно, но это едва ли не самое удобное объяснение. Могли ли они добавить комментарий, объясняющий, что он загружает локальный репозиторий в удаленный, или что-то в этом роде?

Ответы [ 4 ]

4 голосов
/ 24 марта 2019

Все ответы здесь в порядке. Чего не хватает, так это ... вот где приходит твоя напыщенная речь. :-) Твоя цитата профессора здесь подходящая :

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

Или , как предположительно сказал Эйнштейн , «Сделай все как можно проще, но не проще».

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

Фонд также нуждается в некоторой терминологии. Это, вероятно, где страницы руководства Git терпят неудачу больше всего. Они просто распространяют терминологию - иногда несовместимую терминологию, хотя со временем она улучшилась - повсеместно. Это привело к довольно забавным веб-страницам . (Я думаю, что многие введения в Git уклоняются от терминологии, потому что основная основа Git лежит в теории графов и теории хеширования, и люди находят математический аспект этих страшных.)

Сам Git делает вещи сложнее, чем необходимо. Простое доказательство существования этого - Mercurial. Mercurial и Git, по крайней мере, с точки зрения того, что они делают с исходным кодом, одинаково мощны, но у новичков в распределенном управлении исходными кодами намного меньше проблем с запуском в Mercurial, чем в Git. Не на 100% понятно, почему это так, но я думаю, что есть две ключевые вещи, которые Mercurial делает по-другому, которые дают этот результат:

  • В Mercurial филиалы являются глобальными и постоянными. Это очень удобно для начала работы, но, по крайней мере, иногда оказывается ловушкой. В итоге Mercurial добавил закладок , которые работают как ветви Git.

  • У Mercurial нет того, что Git называет index .

Это не единственные вещи - у Git есть много других, меньших раздражений, которых просто нет в Mercurial - но я думаю, что они большие два. Например, весь вопрос о git reset не встречается в Mercurial, потому что git reset (a) манипулирует указателями веток - вместо них у Mercurial есть эти закладки, , если вы решите их использовать, и (b) манипулирует индексом, которого у Mercurial даже нет.

Мой собственный ответ: что происходит

В любом случае, ключ здесь - эти три вещи. (Вот немного терминологии!)

  1. В Git, имя ветки немного больше, чем отображение имени на хеш-идентификатор. Важны коммиты .

  2. A commit - это уникальная сущность, идентифицируемая уникальным хеш-идентификатором, например b5101f929789889c2e536d915698f58d5c5c6b7a, которая хранит - постоянно 1 и неизменным образом - снимок файлов и некоторые метаданные, , включая хеш-код (ы) некоторых других коммитов.

  3. Индекс - это область, которую Git фактически использует для создания новых коммитов.


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


Что нужно знать об индексе

Давайте просто начнемИз этого наблюдения: когда коммит сохраняет снимок всех ваших файлов, он сохраняет их в сжатом виде, доступном только для чтения, и только для Git.Они как бы заморожены или лиофилизированы.Никто не может изменить их вообще.Это хорошо для архивирования - сохранения старого исходного кода - но совершенно бесполезно для выполнения любой новой выполненной работы.

Чтобы выполнить работу, вам нужно место, где ваши файлы не разморожены, обезвожены, читаемы и доступны для записи в их обычной повседневной форме.Это место, которое Git называет вашим рабочим деревом .Git мог бы здесь остановиться - замороженные коммиты и гибкое дерево работы - вот что делает Mercurial, и он работает отлично.Но по какой-то причине Git добавляет эту вещь, которую он называет index , или иногда область подготовки , или даже кеш .(Используемое имя зависит от того, кто / что делает именование, но все три - это одно и то же. Кроме того, сам индекс более сложен, чем я расскажу, но нам не нужно беспокоиться об этих сложностях здесь.)

То, что хранит индекс, - это копии файлов в формате Git.Они не совсем заморожены , но они в том же формате - лиофилизированный формат, как это было.Они бесполезны для вас; они полезны только для Git. Почему это делает спорным, но это делает, и вам нужно знать об этом. Что делает с этим, так это то, что Git делает индекс новый коммит.

Когда вы запускаете:

git commit -m "this is a terrible log message"

Git будет упаковыватьподнимите все, что находится в индексе прямо сейчас , вместе с вашими метаданными - вашим именем и адресом электронной почты, журнальным сообщением и т. д. - и включите его в новый коммит.Материал в рабочем дереве , где вы выполняете свою работу, совершенно не имеет значения!Тот факт, что все уже подготовлено - как бы уже высушено вымораживанием - вот что делает git commit таким быстрымMercurial hg commit, который фиксирует то, что находится в вашем рабочем дереве , должен проверить каждый файл в вашего рабочего дерева, чтобы увидеть, совпадает ли он с предыдущим или нет, иесли нет, подготовьте лиофилизированную форму для фиксации.Таким образом, в большом проекте вы запускаете hg commit, а затем выходите на кофе или что-то еще. 2 Но с Git, если вы измените файл в рабочем дереве, Git сделает you запустить:

git add file

каждый раз.Это копирует файл - во время лиофилизации или Git-ify-его - в индекс.

Следовательно, индекс всегда содержит следующий коммит, который вы предлагаетесделать .Если вы вносите некоторые изменения в рабочее дерево и хотите, чтобы они были в вашем следующем коммите, вам придется явно скопировать их в индекс, прежде чем вы запустите git commit.Вы можете использовать git commit -a, чтобы Git просканировал ваше рабочее дерево и сделал для вас add, заставив Git действовать так же, как Mercurial, если бы вы использовали Mercurial.Это, безусловно, удобно и позволяет вам не думать об индексе или даже делать вид, что его там нет.Но я думаю, что это плохой план, потому что тогда git reset становится необъяснимым.


2 Обычно это не , что плохо, и в маленьком проекте разницапочти не обнаруживается.Mercurial использует множество трюков с кешем, чтобы максимально ускорить это, но - в отличие от Git - он удерживает их подальше от пользователя .


Commits

Теперь давайте внимательно посмотрим, что именно входит в коммит.Я думаю, что лучший способ увидеть это - посмотреть на фактический коммит.Вы можете посмотреть на себя с помощью:

git cat-file -p HEAD

, но я покажу это из Git-репозитория для Git следующим образом:

$ git cat-file -p b5101f929789889c2e536d915698f58d5c5c6b7a | sed 's/@/ /'
tree 3f109f9d1abd310a06dc7409176a4380f16aa5f2
parent a562a119833b7202d5c9b9069d1abb40c1f9b59a
author Junio C Hamano <gitster pobox.com> 1548795295 -0800
committer Junio C Hamano <gitster pobox.com> 1548795295 -0800

Fourth batch after 2.20

Signed-off-by: Junio C Hamano <gitster pobox.com>

Обратите внимание на tree иparent строк, которые ссылаются на дополнительные хэш-идентификаторы. Строка tree представляет сохраненный снимок исходного кода. Это не может быть уникальным! Предположим, вы делаете коммит, а затем возвращаетесь к старой версии, но сохраняете ее как new . Новый коммит может повторно использовать исходного коммита tree, и Git сделает это автоматически. Это один из многих приемов, которые Git использует для сжатия заархивированных снимков.

Строка parent, тем не менее, показывает, как коммиты Git становятся графиком . Этот конкретный коммит b5101f929789889c2e536d915698f58d5c5c6b7a. Коммит, который приходит до , это коммит a562a119833b7202d5c9b9069d1abb40c1f9b59a, который является коммитом слияния:

$ git cat-file -p a562a119833b7202d5c9b9069d1abb40c1f9b59a | sed 's/@/ /'
tree 9e2e07ce274b0a5a070d837c865f6844b1dc0de8
parent 7fa92ba40abbe4236226e7d91e664bbeab8c43f2
parent ad6f028f067673cadadbc2219fcb0bb864300a6c
author Junio C Hamano <gitster pobox.com> 1548794876 -0800
committer Junio C Hamano <gitster pobox.com> 1548794877 -0800

Merge branch 'it/log-format-source'

Custom userformat "log --format" learned %S atom that stands for
the tip the traversal reached the commit from, i.e. --source.

* it/log-format-source:
  log: add %S option (like --source) to log --format

Этот коммит имеет две parent строки, что дает еще два коммита. Вот что делает этот коммит слияния в первую очередь.

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

Мы можем нарисовать это так:

A <-B <-C

для простого трехкомпонентного хранилища или:

...--I--J--M--N
  \       /
   K-----L

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

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

     D
    /
A--B--C

Тогда мы можем согнуть C в сторону и использовать вместо него D:

A--B--D
    \
     C

Это один и тот же график , мы просто смотрим на него по-другому.

Имена ветвей (и другие имена, но мы не будем их здесь описывать)

Эти графические рисунки аккуратны и просты, и, я буду утверждать, способ рассуждать о вашем Git-хранилище. Они показывают коммитов и скрывают от нас безобразные хеш-идентификаторы. Но Git на самом деле нужны хеш-идентификаторы - вот как Git получает коммиты - и нам нужно будет запомнить последний хеш-идентификатор любой из этих цепочек. Причина, по которой нам нужен только последний , должна быть очевидна: если мы ухватимся, скажем, за коммит D, то коммит D сохранит фактический хеш-идентификатор совершить B внутри себя. Поэтому, как только мы узнаем хеш D, мы используем D, чтобы найти B. Затем мы используем B, чтобы найти A, и, поскольку A является самым первым коммитом и поэтому имеет нет родителя, мы можем остановиться и отдохнуть.

Так что нам нужно еще одно дополнение к нашему рисунку здесь. Нам нужно имя ветки . Имя просто указывает на (т. Е. Содержит фактический идентификатор хеша) последний коммит! Мы можем нарисовать это как:

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

Имя , master, содержит идентификатор хэша последнего коммита. Оттуда мы находим предыдущие коммиты. Что Git хранит для нас:

  • все коммиты, по хеш-идентификатору
  • некоторый набор имен, каждое из которых содержит one hash ID

и это - за исключением всех сложностей с индексом и рабочим деревом - так работает Git. Чтобы сделать новый коммит E, мы просто сделаем снимок индекса, добавим метаданные (наше имя, адрес электронной почты и т. Д.), Включая хэш-идентификатор commit D, и запишем его в базу данных коммитов. :

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

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

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

Теперь мы можем выправить излом:

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

А как же бедный одинокий коммит C? У него нет имени. У него есть какой-то большой уродливый хеш-идентификатор, но как, без имени или запоминания этого хеш-идентификатора, мы когда-нибудь найдем коммит C?

Ответ таков: Git в конечном итоге удалит C полностью, если мы не дадим ему имя. Очевидное имя для использования - другое имя ветви, поэтому давайте сделаем это:

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

Теперь у нас есть две ветви: master и dev. Имя master означает"commit E", а имя dev означает"commit C", на данный момент . По мере того, как мы работаем с репозиторием и добавляем в него новые коммиты, хеш-идентификаторы, хранящиеся под этими двумя именами, будут меняться. Это приводит к нашему ключевому наблюдению: В Git коммиты являются постоянными (в основном) и неизменяемыми (полностью), но имена веток move . Git хранят график - эти цепи коммитов с их внутренними стрелами, соединяющими их, таким образом, обращенным назад, - для нас. Мы можем добавить к нему в любое время, добавив больше коммитов. И Git хранит для нас таблицу сопоставления имени-хеш-идентификатора , в которой имена ветвей содержат идентификатор хеша начальные точки (или конечные точки?) На графике.

Термин Git для этих начальных / конечных точек: tip commit . Имя ветви идентифицирует коммит tip.

HEAD и git checkout и индекс и рабочее дерево

Теперь, когда в нашем репозитории имеется более одного имени ветки, нам нужен способ запомнить , какую ветвь мы используем . Это основная функция специального имени HEAD. В Git мы используем от git checkout до , выбираем некоторые существующие имена ветвей, такие как master или dev:

$ git checkout dev

Результат:

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

Прикрепив имя HEAD к имени ветки, например dev, Git знает, над какой веткой мы сейчас работаем.

В качестве важнейшего побочного эффекта Git также :

  • копирует все файлы из C в индекс, готовый к следующему коммиту, и
  • копирует все файлы из C / the-index в рабочее дерево, чтобы мы могли их видеть и использовать.

Git также может понадобиться удалить некоторых файлов, если мы выполняли коммит E и у него есть файлы, которых нет в C. Он удалит их как из индекса, так и из рабочего дерева. Как обычно, Git проверяет, совпадают ли все три копии каждого файла. Если в коммите C есть файл с именем README, например, у нас есть:

  • HEAD:README: это замороженная копия Git-ified в коммите C, теперь доступная под специальным именем HEAD.
  • :README: это индексная копия. Это соответствует HEAD:README на данный момент, но мы можем перезаписать с помощью git add.
  • README: это обычный файл. Мы можем работать с этим. Git не особо заботится об этом - нам нужно скопировать его обратно в :README, если мы его изменим!

Итак, одним действием - git checkout master или git checkout dev - мы:

  • повторно прикрепить HEAD;
  • заполнить указатель; и
  • заполнить рабочее дерево

и теперь готовы к работе, git add файлы для копирования их обратно в индекс и git commit для создания нового снимка, который добавляет к ветви и заставляет ветку name обращаться к новый коммит. Давайте сделаем новый коммит F на dev:

... edit some file(s) including README ...
git add README                    # or git add ., or git add -u, etc
git commit -m "another terrible log message"

и теперь у нас будет:

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

Git знает, что нужно обновлять dev, а не master, потому что HEAD присоединен к dev, а не master. Также обратите внимание, что, поскольку мы сделали коммит F из того, что сейчас находится в нашем индексе, и мы просто сделали индекс соответствующим рабочему дереву, теперь F, индекс и рабочее дерево все совпадают. Это то, что было бы у нас, если бы мы только что запустили git checkout dev!

Здесь git reset входит

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

Предположим, например, что коммит F ужасен - это ошибка, мы просто хотим полностью ее забыть.Нам нужно переместить имя dev, чтобы вместо указания на F оно снова указывало на C - родителя F.

Мы можем найти хеш-идентификаторcommit C и, грубо, просто напишите это прямо в имя ветки.Но если мы сделаем это, как насчет нашего индекса и рабочего дерева?Они по-прежнему будут соответствовать содержимому коммита F.У нас будет график :

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

, но индекс и рабочее дерево не будут совпадать C.Если мы снова запустим git commit, мы получим коммит, который выглядит почти так же, как F - он будет использовать tree, и у него будет просто другая отметка даты и, возможно, лучшее сообщение журнала.Но, может быть, это то, что мы хотим!Может быть, мы хотели просто исправить наше ужасное сообщение в журнале.В этом случае ответом будет создание нового G из текущего индекса.

Это то, что делает git reset --soft: это позволяет нам переместить название ветви , чтобы указать на другоекоммит, без изменения индекса и рабочего дерева.Мы отбрасываем F, затем создаем новый G, который похож на F, но имеет правильное сообщение.F не имеет имени и в конечном итоге отмирает.

Но что, если мы просто хотим полностью избавиться от F?Тогда мы бы хотели, чтобы индекс и рабочее дерево соответствовали коммиту C.Мы позволим F увядать, как и раньше.Но чтобы индекс и рабочее дерево соответствовали C, нам нужно git reset --hard.

Поскольку индекс и рабочее дерево являются отдельными объектами, мы можем выбрать половину пути,Мы можем переместить имя dev, чтобы оно указывало на C, заменить содержимое index на содержимое из C, но оставить рабочее дерево в покое.Это то, что делает git reset --mixed, а git reset --mixed является значением по умолчанию для git reset, поэтому нам даже не нужна деталь --mixed.

Все три из этих действий имеют разные конечные цели: git reset --soft был для повторного выполнения коммита , git reset --hard был для полного исключения коммита , и git reset --mixed не имеет четкого использования в этом конкретном примере.Так почему они все пишутся git reset?Вот где ваша напыщенная речь снова применяется: они, вероятно, не должны быть.Они связаны с тем, что у Git есть три вещи, которые он может делать с хэшем имя-ветви-коммит и содержимое индекса и рабочего дерева:

  1. переместить имя ветви
  2. заменить или сохранить содержимое индекса
  3. заменить или сохранить содержимое рабочего дерева

и git reset либо выполнят шаг 1 и остановятся (git reset --soft), либовыполните шаги 1 и 2 и остановитесь (git reset --mixed / по умолчанию), или выполните все три и остановите (git reset --hard).Но их цели не связаны: Git сбивает с толку механизм (" как мы добираемся отсюда туда") с целью ("добраться туда").

Заключение

Скажите, что история моих коммитов A - B - C, и у меня есть только эта ветвь.

OK:

A--B--C   <-- branch (HEAD)

Мне нужно вернуться к B, но я также хочу сохранить код, который я написал на C

OK.Понятно, что нам нужно одно имя, идентифицирующее коммит B, а другое - фиксация C.Но нам также нужно позаботиться о индексе и рабочем дереве!

Существует только один индекс и одно рабочее дерево, 3 , и эти не скопированы на git clone.Только коммиты являются постоянными.Поэтому, если у вас есть что-то несохраненное в вашем индексе и / или в вашем рабочем дереве, вам, вероятно, следует сохранить его сейчас.(Возможно, с помощью коммитов - и вы можете использовать git stash для коммитов, которые не находятся на любой ветке, но давайте не будем идти туда, по крайней мере, пока.) Давайте предположим, что вы этого не делаетекак убрать вопрос целиком.

чеe график не изменится.Вам просто нужно добавить новое имя.Есть много способов сделать это, но для иллюстрации давайте сделаем это следующим образом: давайте начнем с создания нового имени ветви, которое также указывает на фиксацию C, который мы назовем save,Для этого мы будем использовать git branch, который может создавать новые имена, указывающие на существующие коммиты:

$ git branch save

По умолчанию для новых точек имени используется current commit (через HEAD и текущее имя ветки), так что теперь у нас есть:

A--B--C   <-- branch (HEAD), save

HEAD не переместился: он все еще привязан к branch, что все еще указывает на C.Обратите внимание, что обе ветви идентифицируют одну и ту же фиксацию C, и все три фиксации находятся в обеих ветвях. 4

Теперь, когда у нас есть имя save, сохраняется идентификатор хеша C мы можем переместить имя branch, чтобы указать B.Для этого мы будем использовать git reset.Мы хотели бы, чтобы наш индекс и индекс соответствия рабочего дерева также B, поэтому мы хотим git reset --hard - который заменит наш индекс и рабочее дерево, поэтому важно было сделатьконечно, нам не нужно ничего от них спасать:

$ git reset --hard <hash-of-B>

, давая:

A--B   <-- branch (HEAD)
    \
     C   <-- save

Есть, конечно, много других вариантов.Например, мы можем оставить branch, указывающий на C, и создать новое имя, указывающее на B:

A--B   <-- start-over
    \
     C   <-- branch (HEAD)

, и для этого мы можем использовать:

$ git branch start-over <hash-of-B>

Поскольку мы не двигались HEAD, нет необходимости каким-либо образом нарушать индекс и рабочее дерево.Если бы у нас была незафиксированная работа, мы теперь могли бы запустить git add при необходимости (чтобы обновить индекс при необходимости) и git commit, чтобы сделать новый коммит D, который бы имел C в качестве родителя.


3 Это на самом деле не так.Существует одно главное рабочее дерево и оно имеет один основной индекс.Вы можете создать столько временных индексных файлов, сколько захотите, и, начиная с Git 2.5, вы можете добавлять вспомогательные рабочие деревья в любое время.Каждое добавленное рабочее дерево имеет свой отдельный индекс - в конце концов, индекс индексирует / кэширует рабочее дерево - и свой собственный HEAD, так что каждый может и фактически должен находиться в другой ветви.Но опять же, вам не о чем беспокоиться.Создание временного индекса на самом деле просто для специальных действий: например, как git stash фиксирует текущее рабочее дерево, не мешая другим вещам.

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


Есть хитрость для хеш-идентификаторов

Я просто собираюсь упомянуть об этом здесь мимоходом.Выше у нас было много случаев, когда вам, вероятно, приходилось запускать git log и вырезать и вставлять большие уродливые хеш-идентификаторы.Мы уже знаем, что имя , как и имя ветви, позволяет нам использовать имя вместо идентификатора.Вместо того, чтобы записывать хэш-идентификатор C, на который указывает branch или save, мы можем просто использовать имя:

git show save

, например, извлечет коммит C,затем извлеките коммит B, сравните два и покажите нам, что отличается на снимках в B и C.Но мы можем пойти еще лучше:

git show save~1

означает: Найти коммит C.Затем вернитесь на одну родительскую ссылку. Это коммит B.Так что git show теперь извлечет снимки в B и его parent A, сравнит их и покажет нам, что мы изменили в B.Символы тильды ~ и hat ^ могут использоваться в качестве суффиксов для любого спецификатора редакции .Полное описание того, как задать ревизии (в основном, диапазоны коммитов или коммитов), документировано в руководстве gitrevisions .Есть много способов сделать это!


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

1 голос
/ 24 марта 2019

<rant-adressing on>

Вы пробудились Древние . Быть очень боязливым <rant-adressing off>

; -)


Для рассматриваемой проблемы, если вы хотите сохранить изменения в C на потом, но продолжать работать из состояния B, что лучше, у вас есть несколько способов сделать это. Я бы сделал новую ветку * и сбросил бы старую.

создание веток / метод полного сброса

# create your backup branch for these failed changes
git checkout -b to-be-reviewed

# take your branch to its previous state
git checkout -
git reset --hard HEAD^

Но вы также можете, как вы считали, использовать git reset --soft. Если вы хотите сравнить использование, вот как вы могли бы это сделать:

создание веток / метод мягкого сброса

# undo last commit (C) but keep the changes in the working tree
git reset --soft HEAD^

# create a new branch and commit on it
git checkout -b to-be-reviewed
git commit -m "Your message"

Тогда ваши исходные точки ветвления в точке B и to-be-reviewed имеют все последние (хотя и нерабочие) изменения.


Наконец, это вариант использования для git stash:

нет нового метода ветки / тайника

# reset your branch to state B
git reset --soft HEAD^

# stash your changes with a title for easier reuse
git stash save "Failed changes XYZ"

И в этот момент вы можете позже проверить этот тайник с помощью git stash list / git stash show.

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

1 голос
/ 24 марта 2019

В вашем случае я предлагаю, чтобы 1) вы создали ветку или тег на C, чтобы вы могли просмотреть их, когда захотите, и 2) сбросить ветку на B, чтобы изменения C исключено из ветви.

# create a branch from C
git branch foo C

# create a tag at C
git branch bar C

# reset your branch to B
git checkout <your_branch>
git reset B --hard

# review C
git show foo
git show bar

В некоторой степени тег более стабилен, чем филиал.foo может перейти к другому коммиту по случайной команде.bar всегда указывает на C, если вы не собираетесь переместить его в другой коммит.bar считается псевдонимом C.

1 голос
/ 24 марта 2019

Если вы хотите сохранить коммит C как коммит и снова работать с B, вам потребуется новая ветвь. Я бы предложил сделать новую ветку с C и с жестким сбросом master (или какой-либо другой основной рабочей веткой) на B.

Затем вы останетесь с этим (я добавил новый коммит для ясности):

D      master
| C    review-c branch
|/
B
|
A

Мягкий сброс на B удалит коммит C и внесет изменения, сделанные вами в C (так же, как если бы вы сделали изменения для C самостоятельно и сделали git add из них).

...