В чем разница между commit --amend и reset --soft - PullRequest
1 голос
/ 21 февраля 2020

Идея моего вопроса - исследование ... Какова существенная разница между commit --amend и reset --soft

Процесс расследования по шагам:

vim index.js > edit > save
git add index.js
git commit -m '...'
git push origin

Теперь мне нужно переписать коммит, который я отправил ранее. Для этого я должен использовать:

vim index.js > edit > save
git add index.js
git commit --amend --no-edit
git push --force origin

По сути, у меня есть еще один SHA1 и по аналогии три objects в каталоге .git/objects, но git log показывает мне два SHA1, и я полностью согласен с этим, потому что коммит был исправлен.

Давайте go вернемся немного назад. Вместо git commit --amend я выполнил git reset --soft HEAD~, указатель HEAD обозначает stage версию файла, напишите некоторый код и выполните:

git add index.js > git commit -m '...' > git push --force origin

.git / objects каталог содержит еще один SHA1, но история была дополнена новой.

Итак, я говорю, что команды git commit --amend и git reset --soft имеют одинаковое поведение

Или я не прав?

Ответы [ 2 ]

3 голосов
/ 21 февраля 2020

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

С другой стороны, git reset --soft перемещается указатель HEAD ветви обратно на один коммит, но не затрагивает и этап, и рабочий каталог. Это приводит к тому, что ваша ветка теперь выглядит как предыдущая фиксация, но вся работа, которую вы сделали для генерации старой фиксации HEAD, теперь выглядит как поэтапная. Если вы теперь измените что-то в рабочем каталоге, внесете эти изменения и сделаете коммит, вы также сделаете новый коммит для замены старого HEAD, что аналогично результату git commit --amend.

Одно преимущество, которое git reset --soft (что совпадает с высказыванием git reset --soft HEAD~1) имеет более git commit --amend, это означает, что первый может сбрасываться при множественных коммитах. Это может быть полезно, если вы хотите переписать последние 4 коммитов в вашей ветке. С другой стороны, git commit --amend - это шоу на одной лошади, и оно работает только в коммите HEAD.

0 голосов
/ 21 февраля 2020

Какая существенная разница между коммитом --amend и сбросом --soft

Один выполняет фиксацию, другой выполняет сброс? 100

Это преднамеренный ответ на шутку, но на самом деле он тоже правильный. Это просто разные операции.

Чтобы правильно все это понять, вам нужно понять Git index , как на самом деле Git делает коммиты и как Имена филиалов работают. Это помогает начать с четкого определения, что такое коммит и что он делает.

Git - это все о коммитах; имена веток просто указывают на один коммит, каждый

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

Каждый коммит хранит снимок - полную и полную копию всех вашего файлы - плюс некоторые метаданные, которые предоставляют информацию о коммите: кто его сделал и когда, например. Каждый коммит получает уникальный идентификатор ha sh, зарезервированный навсегда для обозначения , что commit. Каждый Git репозиторий везде согласен с тем, что , что ха sh ID означает , что коммит, и либо репозиторий имеет этот коммит и, следовательно, имеет этот идентификатор ha sh, либо и нет. Однажды сделанный, никакой коммит - фактически, никакой внутренний Git объект - никогда не сможет измениться. Таким образом, моментальный снимок будет заморожен навсегда.

Каждый коммит может содержать в своих метаданных идентификатор ha sh некоторых ранее существовавших ранее коммитов. Это коммит родительский коммит . Большинство коммитов хранит только одного родителя с sh ID. Эти связи, от дочерних к родительским, образуют цепочки:

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

Если мы на коммите H, мы можем прочитать его родительский ха sh ID G. Это позволяет Git искать commit G; G содержит идентификатор ha sh его родителя, F. Это позволяет Git искать коммит F, который содержит его родительский идентификатор ha sh. Повторяя этот процесс, Git может работать весь путь от last назад до first one. 2

Это означает, что нам нужно помнить только последний коммит. Может быть несколько «последних коммитов», как в этом случае:

...--F--G--H   <-- master
         \
          I--J   <-- develop

Обратите внимание, что H - последний коммит на master, а J - последний коммит на develop. имена ветвей выбирают эти tip коммиты. Отсюда Git может работать в обратном направлении. Обратите внимание, что commit G находится на обеих ветвях; это, возможно, будет понятнее, если мы будем рисовать H на строке отдельно:

          H   <-- master
         /
...--F--G
         \
          I--J   <-- develop

(эти два рисунка представляют один и тот же репозиторий).

Когда существует более одной ветви, подобной этой нам нужен способ узнать , какой из них мы извлекли с git checkout <em>branch</em> или новомодный git switch <em>branch</em>. Чтобы отслеживать, мы можем нарисовать диаграмму со специальным именем HEAD, написанную заглавными буквами, вот так, прикрепленную к одному из названий ветвей:

...--F--G--H   <-- master (HEAD)
         \
          I--J   <-- develop

Этот рисунок означает, что мы находимся на ветке master и зафиксировали H.

...--F--G--H   <-- master
         \
          I--J   <-- develop (HEAD)

Этот рисунок означает, что мы находимся на ветке develop и получили фиксацию J.


1 Коммиты можно разбить на составные части - объекты дерева, объекты BLOB-объектов и базовые объекты коммитов, каждый из которых ссылается на объект дерева - но этот уровень не там, где пользователи работают с Git.

2 Некоторые коммиты, которые Git вызывает слияние коммитов, содержат два или более идентификатора ha sh. От такого коммита Git работает обратно к обоим (или всем) родителям, вводя разворот в историю. Обратите внимание, как слияние, которое объединяет вещи, действует как расходящаяся точка, потому что Git работает в обратном направлении. Когда ветви расходятся, так как Git работает в обратном направлении, этот обратный обход объединяет их.

По крайней мере один коммит в каждом репозитории имеет нет родителя, потому что это был первый коммит в истории, и не мог указать назад. Коммит без родителя - это root commit .


Мы делаем новые коммиты из index

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

Это означает, что Git должен извлечь файлы из коммита куда-нибудь, чтобы они стали полезными. Это где-то ваша рабочая область, которая Git называет ваше рабочее дерево или рабочее дерево . Здесь ваши файлы имеют свою повседневную форму: они не заморожены и не сжаты; любая компьютерная программа может использовать их. С вашим рабочим деревом вы можете делать все, что захотите: в конце концов, с ним будет работать ваше .

Git может делать новые коммиты из вашей работы -tree. Другие системы контроля версий делают это. Но Git нет. Вместо этого, где-то между текущим коммитом *1143* - тем, который вы извлекли, из ветки, которую вы извлекли, которую Git находит со специальным именем HEAD, как мы рисовали выше - и рабочее дерево Git хранит все ваши файлы в специальной области, которая Git вызывает, по-разному, index или промежуточная область , или - редко в наши дни - кеш .

Все эти три имени относятся к одному и тому же. Индекс или промежуточная область - я назову здесь index - содержит копии всех ваших подтвержденных файлов, по крайней мере, на начальном этапе. Они в замороженном формате, как и в коммите, 4 , но в отличие от коммита, они на самом деле не заморожены: вы можете перезаписать их.

Итак, каждый файл имеет три активные версии: копия HEAD, замороженная в текущем коммите; индексная копия, которую вы можете заменить в любое время; и копия рабочего дерева, которую вы можете видеть и работать с ней. Вы редактируете копию рабочего дерева, затем запускаете git add <em>file</em>. Вы должны запускать git add все время, и причина теперь ясна: каждый git add, который вы запускаете копирует файл, из рабочего дерева - где он имеет повседневную форму, которую использует ваш компьютер - в индексную / промежуточную область, где она находится в замороженном виде Git лайков.

Теперь мы можем видеть, что делает git commit, и почему это так быстро. 5 Все git commit должен упаковать то, что уже в индексе в нужном формате, в новый коммит. Ну, во-первых, он должен собрать сообщение журнала и добавить свое имя, текущую дату и время и все такое; и он должен установить родительский идентификатор нового коммита ha sh ID в метаданных коммита. Затем он может сделать коммит, используя предварительно замороженные файлы из индекса.

parent нового коммита - это текущий коммит (за исключением случаев, когда мы посмотрим, за --amend). Новый коммит - скажем, K - записывается в коллекцию всех коммитов и указывает на текущий коммит:

...--F--G--H   <-- master
         \
          I--J   <-- develop (HEAD)
              \
               K

, и теперь происходит бит magi c: Git записывает новый идентификатор ha sh коммита в имя , к которому прикреплен HEAD . В данном случае это develop, поэтому теперь у нас есть:

...--F--G--H   <-- master
         \
          I--J--K   <-- develop (HEAD)

и K - последний коммит develop.


3 Обратите внимание, что даже git commit --amend не не меняет коммит! Мы скоро доберемся до того, что он делает, но вот подсказка. Если вы берете коммит, изменяете что-то и используете его для создания нового коммита, вы получаете другой коммит с другим идентификатором ha sh. Не имеет значения что вы меняете (за исключением того, что разные изменения приводят к разным идентификаторам га sh): любой другой коммит будет, по определению в Git, иметь другой га sh ID. Только если вы сохраните все последние биты одинаковыми - один и тот же снимок, один и тот же автор, одно и то же сообщение журнала и одну и ту же отметку даты и времени - вы получите исходный идентификатор ha sh. Но тогда вы не сделали новый коммит: вы снова сделали старый коммит, с тем же родителем, тем же снимком, тем же сообщением журнала и даже теми же временными метками. Вы сделали первоначальный коммит вчера, и вы только что сделали новый вчера - снова тот же коммит!

4 Технически, индекс содержит ссылки на замороженный копии: он просто содержит BLOB-объект ha sh ID, а также имя файла и кучу кэшированной информации о рабочем дереве (отсюда и название cache ). Разница проявляется, если и когда вы начнете ковыряться с git ls-files --stage и git update-index и т.п., чтобы посмотреть или изменить то, что находится в индексе. Однако, за исключением этих случаев, вы можете просто думать об индексе как о хранении копии каждого файла.

5 Если вы когда-либо использовали некоторые другие до Git В системах управления версиями вы, возможно, помните, как вы можете ввести команду типа «принять» или «оформить заказ» и go отправиться на ланч, потому что на это уйдет много секунд или минут. В наши дни некоторые люди думают, что Git медленно: они не знают медленно . 12


Что git commit --amend делает

Все, что на самом деле делает git commit --amend:

  • пишет новый коммит почти как обычно, за исключением
  • вместо использования текущего коммита в качестве родителя нового коммита, используйте родитель (ы) текущего коммита в качестве родителя (ей) нового коммита.

Кроме того, он по умолчанию позволяет нам редактировать сообщение о коммите текущего коммита при создании нового коммита.

Итак, предположим, что у нас есть:

...--F--G--H   <-- master
         \
          I--J--K   <-- develop (HEAD)

и вы понимаете, что забыли git add файл или хотите исправить сообщение фиксации. Вы делаете забытый git add, если необходимо, затем запускаете:

git commit --amend

Git идет, чтобы собрать сообщение коммита, но на этот раз он открывает редактор файла, содержащего коммит K commit сообщение. Вы можете отредактировать это при необходимости, выписать его и выйти из редактора, и git commit сделает новый коммит, но вместо установки его родителя на K, он устанавливает своего родителя на родительский (ие) K. , что означает J. Это делает новый коммит, который мы можем назвать L или K'; давайте использовать K'. В качестве последнего шага git commit записывает K' ID ha sh в текущее имя ветви:

...--F--G--H   <-- master
         \
          I--J--K'  <-- develop (HEAD)
              \
               K   [abandoned]

Обратите внимание, что commit K все еще существует в хранилище. Просто нет имени ветви , по которому мы можем найти коммит K. 6 Имя develop теперь указывает на коммит K'.

Итак, git commit --amend появляется для изменения коммита. Но все, что он на самом деле делает, это отодвигает коммит в сторону, помещая вместо него новую и улучшенную (ну, по-видимому) замену.


6 Мы можем найти K У ha sh ID в reflogs: в reflog для develop он есть, в develop@{1}, в данный момент, и в reflog для HEAD, в HEAD@{1}, в данный момент. Большинство команд Git не смотрят на reflogs, хотя - и reflogs не являются обязательными, в этом отношении. Срок действия записей reflog истекает, и как только они исчезают, коммит K становится незащищенным от Git Grim Collector, git gc, мусор которого собирает оставленные и незащищенные коммиты и другие потерянные Git objects.

В итоге это означает, что обычно вы можете вернуть потерянные коммиты как минимум на 30 дней, так как это минимальное время хранения записи reflog по умолчанию. git gc обычно обрабатывает все это, включая устаревшие старые записи reflog, и Git запускает git gc автоматически, иногда, если и когда Git считает, что это может быть полезно.


git reset перемещает имена ветвей и, при необходимости, сбрасывает индекс

Команда git reset значительно сложнее, чем git commit --amend, в основном потому, что слишком много отдельных действий встроено в одно git reset команда. Если мы проигнорируем большинство из них и сконцентрируемся на наиболее фундаментальном режиме работы git reset, то git reset сделает только три вещи:

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

  2. Затем, если вы сказали --soft, git reset останавливается. В противном случае он продолжает загружать index из коммита, на который вы только что сказали ему перейти.

  3. Затем, если вы сказали --mixed - или нет Не могу сказать ни одного из этих ... 1344 * остановок. В противном случае, оно продолжит, чтобы ваше рабочее дерево соответствовало обновлению, внесенному в индекс.

Итак, если мы посмотрим на этот график:

...--F--G--H   <-- master
         \
          I--J--K   <-- develop (HEAD)

и запустите git reset --soft HEAD~1, выбранный нами коммит был J: HEAD~1 означает найти коммит, который HEAD выберет (то есть K) и отступите на один , который приземлится на J. Итак, шаг 1 из git reset означает переместить develop в J, что дает нам следующее:

...--F--G--H   <-- master
         \
          I--J   <-- develop (HEAD)
              \
               K   [abandoned]

Обратите внимание, что это выглядит очень похоже на то, что мы получили от git commit --amend за исключением того, что здесь нет коммита K'.

Мы сказали git reset сбросить с помощью --soft, поэтому на шаге 2, который приведет к сбросу индекса, он просто завершится. Индекс 1379 * оставлен в покое. Наше рабочее дерево оставлено в покое. Если индекс совпадал с коммитом K минуту назад - и, вероятно, это было так - тогда он по-прежнему соответствует коммиту K. Если наше дерево работы соответствует коммиту K, оно все равно работает. (Если нет, то рабочее дерево сейчас не имеет значения.)

Если мы сейчас запустим git commit, Git будет собирать сообщения журнала как обычно, упаковывать все, что есть в индексе - что, вероятно, все еще соответствует K - и сделать новый коммит. Давайте назовем этот коммит K' и нарисуем его:

...--F--G--H   <-- master
         \
          I--J--K'  <-- develop (HEAD)
              \
               K   [abandoned]

В итоге мы получили то же самое, что получили бы git commit --amend: новый коммит K' (с независимо от того, есть ли у него sh ID), чьим родителем является J и чьим содержимым является то, что было в индексе.

Так что --amend и reset-and-commit одинаковы ... кроме случаев, когда они

Версия --amend проще: мы запускаем одну команду. Это также позволяет нам изменить коммит слияния . Предположим, например, что у нас есть это:

          I--J
         /    \
...--G--H      M   <-- branch (HEAD)
         \    /
          K--L

Мы можем использовать git commit --amend, чтобы отодвинуть M в сторону и сделать новый коммит M', используя содержимое индекса (вероятно, такое же, как * 1410). * снимок) и новое сообщение журнала. Когда мы это сделаем, мы получим M' с родителями J и L: два родителя, т.е. коммит слияния. Без --amend довольно сложно получить коммит слияния, 7 и git reset --soft, а другой коммит не сделает этого.

По тому же признаку, git commit --amend будет только оглядываться назад один коммит. Используя git reset --soft, мы можем сделать кучу коммитов "go away". Предположим, например, что у нас есть это:

...--o--*--o   <-- master
         \
          A--B--C--D--E--F--G--H--I   <-- feature (HEAD)

, где вся длинная цепь от A до I была группой экспериментов. Эта функция теперь работает, и вы хотели бы иметь один коммит AI, который делает все это.

Есть несколько способов достичь этого, 8 , но если вы только что сделали коммит I, так что ваш индекс и коммит рабочего дерева соответствуют I, теперь вы можете git reset --soft HEAD~9 имя feature, чтобы оно указывало на коммит *. Затем вы можете git commit, используя текущий индекс - снимок с I, - сделать новый коммит AI:

          AI   <-- feature (HEAD)
         /
...--o--*--o   <-- master
         \
          A--B--C--D--E--F--G--H--I   [abandoned]

Фиксы A - I остаются в вашем хранилище, можно найти только с помощью повторных флагов feature и HEAD, в течение еще 30+ дней, если они понадобятся вам назад; но теперь git log master..feature показывает только один коммит AI. Снимок в AI соответствует снимку в I, но похоже, что вы все сделали за один потрясающий коммит.


7 Git как набор инструментов То есть, есть несколько способов сделать новый коммит "слиянием". Самое простое - это опуститься ниже уровня git commit, в составные части, которые делают новый коммит, но вы также можете создать файл .git/MERGE_HEAD с идентификаторами ha sh в нем. Однако все это не предназначено для повседневного повседневного использования.

8 Обычным является использование git merge --squash, что позволяет заставить AI следовать после окончания master, но, как правило, использует новое имя ветви:

             AI   <-- completed-feature (HEAD)
            /
...--o--*--o   <-- master
         \
          A--B--C--D--E--F--G--H--I   <-- feature

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

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