Допустим, у вас есть последовательность коммитов с сообщениями вида “упс.”, “В работе” и “позабыл этот файл”. Вы можете использовать reset для того, чтобы просто и быстро слить их в один. (В Объединение коммитов представлен другой способ сделать то же самое, но в данном примере проще воспользоваться reset.)
Предположим, у вас есть проект, в котором первый коммит содержит один файл, второй коммит добавляет новый файл и изменяет первый, а третий коммит снова изменяет первый файл. Второй коммит был сделан в процессе работы и вы хотите слить его со следующим.

Вы можете выполнить git reset --soft HEAD~2, чтобы вернуть ветку HEAD на какой-то из предыдущих коммитов (на первый коммит, который вы хотите оставить):

Затем просто снова выполните git commit:

Теперь вы можете видеть, что ваша “достижимая” история (история, которую вы впоследствии отправите на сервер), сейчас выглядит так – у вас есть первый коммит с файлом file-a.txt версии v1, и второй, который изменяет файл file-a.txt до версии v3и добавляет file-b.txt. Коммита, который содержал файл версии v2не осталось в истории.
Наконец, вы можете задаться вопросом, в чем же состоит отличие между checkout и reset. Как и reset, команда checkout управляет тремя деревьями Git, и также ее поведение зависит от того указали ли вы путь до файла или нет.
Команда git checkout [branch] очень похожа на git reset --hard [branch], в процессе их выполнения все три дерева изменяются так, чтобы выглядеть как [branch]. Но между этими командами есть два важных отличия.
Во-первых, в отличие от reset --hard, команда checkout бережно относится к рабочему каталогу, и проверяет, что она не трогает файлы, в которых есть изменения. В действительности, эта команда поступает немного умнее – она пытается выполнить в Рабочем Каталоге простые слияния так, чтобы все файлы, которые вы не изменяли, были обновлены. С другой стороны, команда reset --hard просто заменяет всё целиком, не выполняя проверок.
Второе важное отличие заключается в том, как эти команды обновляют HEAD. В то время как reset перемещает ветку, на которую указывает HEAD, команда checkout перемещает сам HEAD так, чтобы он указывал на другую ветку.
Например, пусть у нас есть ветки master и develop, которые указывают на разные коммиты и мы сейчас находимся на ветке develop (то есть HEAD указывает на нее). Если мы выполним git reset master, сама ветка develop станет ссылаться на тот же коммит, что и master. Если мы выполним git checkout master, то develop не изменится, но изменится HEAD. Он станет указывать на master.
Итак, в обоих случаях мы перемещаем HEAD на коммит A, но важное отличие состоит в том, как мы это делаем. Команда reset переместит также и ветку, на которую указывает HEAD, а checkout перемещает только сам HEAD.

Другой способ выполнить checkout состоит в том, чтобы указать путь до файла. В этом случае, как и для команды reset, HEAD не перемещается. Эта команда как и git reset [branch] file обновляет файл в индексе версией из коммита, но дополнительно она обновляет и файл в рабочем каталоге. То же самое сделала бы команда git reset --hard [branch] file (если бы reset можно было бы так запускать) – это небезопасно для рабочего каталога и не перемещает HEAD.
Также как git reset и git add, команда checkout принимает опцию --patch для того, чтобы позволить вам избирательно откатить измения содержимого файла по частям.
Надеюсь, вы разобрались с командой reset и можете ее спокойно использовать. Но, возможно, вы всё еще немного путаетесь, чем именно она отличается от checkout, и не запомнили всех правил, используемых в различных вариантах вызова.
Ниже приведена памятка того, как эти команды воздействуют на каждое из деревьев. В столбце “HEAD” указывается “REF” если эта команда перемещает ссылку (ветку), на которую HEAD указывает, и “HEAD” если перемещается только сам HEAD. Обратите особое внимание на столбец “Сохранность РК?” – если в нем указано NO, то хорошенько подумайте прежде чем выполнить эту команду.
Читать дальше