git rebase -i. Например, мы хотим схлопнуть последние 2 коммита в один, и изменить ему сообщение. Для этого делаем:
git rebase -i HEAD~2, где после тильды указываются число коммитов от последнего - HEAD:pick d60786481 commit 1 name
pick 7a9d27f04 commit 2 name
# Rebase 889cd8739..7a9d27f04 onto 889cd8739 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
Сначала идёт описание 2-х коммитов, а ниже, в комментарии, действия, которые мы можем над ними выполнить. Нам нужно выполнить squash, поэтому вместо pick пишем s или squash, сохраняем файл и открывается новый, где можно будет указать новое commit-сообщение. Указываем, сохраняем, профит. Если изменения были локально и не уходили в удалённый репозиторий - можно просто залить. Если уже уходили - то в push перед именем ветки ставим "+" - сокращения флага --force.
3. Интересная вещь - хэши коммитов. Непонимание того, как git отслеживает изменения, ведут к конфликам в коде. Например: у тебя есть dev и master-ветки. Тебе нужно что-то поправить, что уже ушло в master. Логично, что эти же изменения нужно залить и в dev. Бывает, что проще сначала сделать ветку от dev, сделать правку там (допустим, поправить надо конфиг в паре строк), залить в dev. Затем сделать ветку от master, поправить там и залить в master. Вроде бы задача решена, но в следующий раз, когда ты будешь вливать что-то из dev в master, git может начать ругаться, несмотря на то, что код полностью идентичен. Произойдёт это из-за того, что у коммитов не совпадут хеши, а хеши - это одно из условий проверки кода на конфликты. Придётся подтягивать изменения, делать rebase, заливать опять. В таком случае правильный вариант - это сделать изменения в dev, залить. Затем перейти в master, сделать rebase или merge на dev, и залить и туда. И обоих ветках будет одинаковый коммит и в последующем ошибок не возникнет.
git - мощнейший инструмент, и вышеописанные команды - только верхушка айсберга. Если подобный формат разрабора команд и разных фишек - понравится, в последующем буду чаще писать о том, на что способна эта система контроля версий.