Основам git мне пришлось научиться на своем первом месте работы (около трех лет назад).
С тех пор я считал, что для полноценной работы нужно запомнить всего-лишь несколько команд:
git add <path>
git commit
git checkout <path/branch>
git checkout -b <new branch>
И дополнительно:
git push/pull
git merge <branch>
git rebase master
(а что, можно еще и на другие ветки ребейзить? О_о)В принципе, я и сейчас во многом так считаю, но со временем волей-неволей начинаешь узнавать интересные трюки.
Вообще, имеет смысл подробнее разузнать о понятиях гита. Лучше подробнее ознакомиться с концепцией коммитов, что такое ветка, что такое тег и пр.
Удивительно, но не у всех оно есть. Отправляемся в гугл по запросу "git_completion", скачиваем скрипт и действуем по инструкции к нему.
Данный код нужно добавить в .bashrc
. Он со мной с некоторыми изменениями путешествует еще с того самого первого места работы.
function git-current-branch {
git branch --no-color 2> /dev/null | grep \* | colrm 1 2
}
function set_prompt_line {
local BLUE="\[\033[0;34m\]"
# OPTIONAL - if you want to use any of these other colors:
local RED="\[\033[0;31m\]"
local LIGHT_RED="\[\033[1;31m\]"
local GREEN="\[\033[0;32m\]"
local LIGHT_GREEN="\[\033[1;32m\]"
local WHITE="\[\033[1;37m\]"
local LIGHT_GRAY="\[\033[0;37m\]"
# END OPTIONAL
local DEFAULT="\[\033[0m\]"
export PS1="$BLUE\w $LIGHT_RED[\$(git-current-branch)]$DEFAULT \$ "
}
set_prompt_line
Для справки: за внешний вид командной строки баша отвечает переменная PS1. Have fun.
Вообще-то, у гита есть свои алиасы, но я понятия не имею, как их добавлять, т.к. мне лень изучать вопрос и не нравится использование команды git
. Я пользуюсь башем:
#
# Git
#
alias current-branch='git-current-branch'
alias git-uncommit='git reset --soft $(git log --format=%H -2 | tail -1)'
alias gst='git status'
alias glog='git log'
alias gcheck='git checkout'
alias gamend='git commit --amend'
__git_complete gcheck _git_checkout
alias gcom='git commit'
__git_complete gcom _git_commit
alias gdiff='git diff'
__git_complete gdiff _git_diff
alias gadd='git add'
__git_complete gadd _git_add
Обратите внимание на __git_complete <something> <another>
. Эта команда включает гитовое автодополнение для алиаса.
Для начала небольшая страшилка, основанная на реальных событиях:
Как-то раз молодой неопытный программист хотел впервые закоммитить код, а гит открыл ему vim!
Да, история произошла со мной. Через несколько часов я смог его закрыть и начал коммитить только с однострочными комментариями через git commit -m
.
Git, как и некоторые другие утилиты (crontab, например) проверяют наличие переменной EDITOR.
В конфиге баша (~/.bashrc
) можно добавить вот такую строчку:
export EDITOR=<команда, открывающая ваш текстовый редактор>
У меня это emacsclient
, раньше был subl
(Sublime Text). Я не проверял, но я полагаю, что очень важно, чтобы команда не возвращала управление терминалу, пока текстовый файл не будет закрыт.
Иногда можно просто сменить ветку, но иногда возникают конфликты. Я знаю два варианта:
1) Сделать временный коммит
2) git stash
, сменить ветку, ..., вернуть ветку, git stash pop
Первый вариант надежнее, второй удобнее (имхо).
git diff
Показывает ваши изменения относительно текущего коммита + stage (важное уточнение). Замечание: в дифф не попадают новые файлы
git diff --cached
Замечание: сюда новые файлы попадают.
Т.е. файлы, которые не относятся к репозиторию
git clean -df
-d
— удаляет еще и директории-f
— обязательная опция, без нее гит попросту откажется что-либо удалять (уж не знаю, зачем она)У меня на это дело есть alias в баше:
alias git-uncommit='git reset --soft $(git log --format=%H -2 | tail -1)'
git reset --soft <commit/branch/tag>
переносит ветку на коммит, но код не меняет. Разница заносится в stage.
$(<whatever>)
— баш выполняет содержимое скобочек и подставляет результат выполнения вместо всего выражения. Например, cat $(ls | tail -1)
выдаст содержимое последнего файла из ls.
git log --format=%H -2
выдаст хеши двух последних коммитов.
В общем, вся команда сводится к тому, что текущая ветка переносится на один коммит назад, а изменения, внесенные коммитом, попадают в stage
upd. Как многие справедливо заметили, того же результата можно добиться намного проще: git reset --soft HEAD~
— данная команда берет предыдущий коммит от "головы" и "ресетит" гит на него. В свое оправдание могу сказать, что этому алиасу пара лет и в то время я не знал о том, что такое HEAD
и тем более HEAD~
Когда я работаю на своей ветке, периодически я делаю несколько коммитов, которые совсем не имеют смысла по отдельности (а делаю я это просто для того, чтобы коммитить почаще и не терять мысль), поэтому перед вливанием их в мастер имеет смысл их объединить
Решение:
Интерактивный rebase!
git rebase -i master
Это откроет текстовый редактор, в котором списком будут указаны коммиты.
Вы можете:
В данной ситуации нужно взять нужные коммиты, расставить их друг за другом и всем, кроме первого, поставить пометку squash
.
Вообще я после каждой фичи делаю интерактивный ребейз и смотрю, какие коммиты я хочу объединить, какие переставить для красоты, какие поправить. Это позволяет сохранять красоту в версионировании.
git add <forgotten changes>
git commit --amend
Еще стоит упомянуть:
git commit --amend --no-edit # Не редактировать сообщение
git commit --amend -m 'my commit message' # работает так, как вы ожидаете
Ситуация:
3 коммита назад допустил опечатку, не хочу, чтобы это позорище кто-то увидел отдельным коммитом.
Решение:
Интерактивный rebase!
git rebase -i HEAD~3
Лично я в указанной ситуации (а у меня она часто возникает) делаю так: создаю коммит, где в сообщении добавляю префикс[to_squash]
, заканчиваю работу над веткой, делаю полный ребейз ветки на мастер (git rebase -i master
) и переношу этот коммит под тот, к которому данная правка относится, с пометкой s
(squash).
Коммиты желательно делать максимально простыми (антоним слову "сложными"). Хочу вот я на гитхабе посмотреть, какая история у файла hello_world.rb
, смотрю историю, а там среди прочих коммит "create super-booper feature", в котором в файле hello_world.rb у одной переменной изменено имя, хотя она к фиче совсем отношения не имеет. Лучше было бы наличие коммита "rename variable x to y in hello_world.rb".
Собственно, например, у меня есть код:
def kvadrat(x)
x * x
end
puts kvadrat(n)
Мне нужно добавить фичу: выводить удвоенное n. Изи! Но пока я пишу фичу, на автомате меняю некрасивое имя функции.
Пишем:
def square(x)
x * x
end
def double(x)
x + x
end
puts square(n)
puts double(n)
Как теперь коммитить? Можно быстро вернуть старое название, закоммитить новый функционал, а потом уже переименовать, но это не всегда уместно, т.к. изменения могут быть достаточно крупными. Можно честно признать, что коммит сложный и написать сообщение в духе "добавил фичу + переименовал метод", но мы ведь стараемся делать коммиты простыми, верно?
Но у гита есть отличная команда:
git add -p
Она интерактивная. Поочередно берет изменения кусками (hunk) и спрашивает, что с данным куском делать: игнорировать, добавить, изменить и добавить. Третий вариант достаточно мощный, можно по отдельности добавлять изменения даже в рамках одной строчки (kvadrat(x) + kub(x)
=> square(x) + cube(x)
в 2 коммита).
Я не буду приводить пример, просто зайдите в любой ваш проект с гитом, отредактируйте пару файлов в разных местах и введите эту команду. Иногда лучше один раз попробовать, чем сто раз услышать (при работе команды можно ввести ?
для краткой справки)
git reflog
— меня это спасло, когда я случайно удалил ветку, не смерджив и не запушив ееgit rebase -i
— в посте указан лишь частный случай применения.git log --graph
— просто он забавный. Не знаю, есть ли практическое применение.git cherry-pick <commit>
— пытается применить изменения коммита к текущемуЯ указал здесь всего-лишь парочку "трюков" работы с git, но их я использую на ежедневной основе.
Смысл данного поста (помимо того, чтобы ублажить свое ЧСВ и оставить заметку для себя самого) в том, чтобы еще раз подчеркнуть известную (относительно) фразу: Know your tools!.
В гите (да и в принципе во всех ваших инструментах для работы) есть множество деталей, которые могут сделать вашу жизнь проще. Например, я видел репозитории, в которых стояли валидации для коммитов: нельзя было ничего закоммитить, если не проходили тесты или тесты не покрывают изменения.
К сожалению, не доступен сервер mySQL