Если вы регулярно используете Git, то вам могут быть полезны практические советы из этой статьи. Если вы в этом пока новичок, то для начала вам лучше ознакомиться с Git Cheat Sheet. Скажем так, данная статья предназначена для тех, у кого есть опыт использования Git от трёх месяцев. Осторожно: траффик, большие картинки!

Содержание:
  1. Параметры для удобного просмотра лога
  2. Вывод актуальных изменений в файл
  3. Просмотр изменений в определённых строках файла
  4. Просмотр ещё не влитых в родительскую ветку изменений
  5. Извлечение файла из другой ветки
  6. Пара слов о ребейзе
  7. Сохранение структуры ветки после локального мержа
  8. Исправление последнего коммита вместо создания нового
  9. Три состояния в Git и переключение между ними
  10. Мягкая отмена коммитов
  11. Просмотр диффов для всего проекта (а не по одному файлу за раз) с помощью сторонних инструментов
  12. Игнорирование пробелов
  13. Добавление определённых изменений из файла
  14. Поиск и удаление старых веток
  15. Откладывание изменений определённых файлов
  16. Хорошие примечания к коммиту
  17. Автодополнения команд Git
  18. Создание алиасов для часто используемых команд
  19. Быстрый поиск плохого коммита


1. Параметры для удобного просмотра лога


Пример команды: git log --oneline --graph

Скорее всего, вам уже доводилось использовать команду git log. У неё есть много полезных параметров, которые можно комбинировать друг с другом для достижения наилучшего результата. Чаще всего я использую эти:
  • --author="Alex Kras" — выводит коммиты, сделанные конкретным человеком
  • --name-only — выводит только названия изменённых файлов
  • --oneline — выводит сокращённые данные коммита (в виде одной строки)
  • --graph — выводит дерево зависимостей для всех коммитов
  • --reverse — выводит коммиты в обратном хронологическом порядке (сначала старые)
  • --after — выводит коммиты, сделанные после определённой даты
  • --before — выводит коммиты, сделанные до определённой даты

К примеру, когда-то у меня был руководитель, который требовал еженедельные отчёты каждую пятницу. И мне было достаточно написать git log --author="Alex Kras" --after="1 week ago" --oneline, немного подредактировать полученный результат и отправить их начальству.

Вообще, в Git есть много всяких полезных параметров. Просто попробуйте выполнить man git-log чтобы посмотреть все варианты просмотра истории. Если ни один из предложенных вариантов вас не устроит, вы всегда можете воспользоваться параметром --pretty, с помощью которого можно настраивать выдачу в широких пределах.



2. Вывод актуальных изменений в файл


Пример команды: git log -p filename

С помощью команд git log -p или git log -p filename можно посмотреть не только примечание к коммиту, автора и дату, но также сделанные в этом коммите изменения.

Далее можно использовать функцию поиска утилиты less, набрав «слеш» и введя поисковый запрос: /{{поисковый-запрос}} (используйте маленькую "n" для перехода к следующему результату поиска и большую "N" для того, чтобы вернуться к предыдущему):



3. Просмотр изменений в определённых строках файла


Пример команды: git log -L 1,1:some-file.txt

С помощью команды git blame filename можно определить автора последних изменений для каждой строки в файле.



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

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

По сути это аналог команды git log -p, только с фокусом на конкретных изменениях.



4. Просмотр ещё не влитых в родительскую ветку изменений


Пример команды: git log --no-merges master..

Если вам приходилось работать с долгоживущими ветками, над которыми трудится много людей, то вы наверняка сталкивались с множественными вливаниями (мёржами) родительской ветки (например, master) в ветку с разрабатываемой фичей. Такие мёржи затрудняют просмотр истории изменений рабочей ветки, потому что будет сложно отличить коммиты, сделанные в родительской ветке от коммитов рабочей ветки.

Команда git log --no-merges master.. решает эту проблему: параметр --no-merges говорит, что нужно показать коммиты, которые ещё не были вмёржены куда-либо, а параметр master.. говорит, что нужно показать только те изменения, которые ещё не были вмёржены в ветку master (здесь важно обратить внимание на две точки после названия ветки).

Чтобы посмотреть не только коммиты, но и изменения, которые были в них внесены, можно использовать команды git show --no-merges master.. или git log -p --no-merges master.. (их вывод будет идентичным).

5. Извлечение файла из другой ветки


Пример команды: git show some-branch:some-file.js

Иногда бывает удобно посмотреть на какой-либо файл в другой ветке, не переключаясь на неё. Это можно сделать с помощью команды git show some-branch-name:some-file-name.js, которая выведет содержимое файла в указанной ветке прямо в терминал.

А с помощью перенаправления вывода можно сохранить этот файл в указанное место на диске, например, если вы заходите открыть два файла одновременно в своём редакторе: git show some-branch-name:some-file-name.js > deleteme.js.

Примечание: если вам нужно всего лишь сравнить два файла, то можно выполнить такую команду: git diff some-branch some-filename.js

6. Пара слов о ребейзе


Пример команды: git pull --rebase

Ранее мы говорили о многочисленных мёржах мастера в рабочую ветку. Некоторых из них можно избежать, используя команду git rebase. Вообще, ребейз — очень мощная функция, и, пожалуй, будет лучше оставить её подробное описание для отдельного поста. Вот, например, что говорится в книге «Pro Git»:
Несмотря на все свои преимущества, у ребейза есть и свои недостатки, которые можно выразить одним предложением:

Не делайте ребейз коммитов, находящиеся вне вашего репозитория.

Если вы последуете этому совету, то всё будет хорошо. В противном случае все будут вас ненавидеть, а друзья и семья станут вас презирать.

Однако ребейза не нужно бояться, просто следует соблюдать осторожность при работе с ним.

Пожалуй, лучший способ сделать ребейз — использовать его интерактивный режим, запускаемый с помощью команды git rebase -i {{some commit hash}}, которая откроет редактор с инструкциями о том, что и как делать. Не буду дальше вдаваться в подробности, поскольку тема эта достаточно объёмная.



Ещё одна полезная команда ребейза: git pull --rebase

Например, вы работаете с локальной копией ветки и сделали небольшой коммит. А в это время кто-то ещё залил в удалённую копию ветки результаты своего недельного труда. Когда вы попытаетесь запушить свои изменения, Git скажет вам, что он не может это сделать, и что вам сначала нужно сделать git pull для разрешения конфликта. Как добропорядочный человек вы так и поступите и после выполнения команды git pull в истории вашей локальной копии ветки получится вот такой вот коммит, сгенерированный автоматически: «Merge remote-tracking branch 'origin/master'».

Это, конечно, не проблема и всё будет работать как нужно, однако такие коммиты засоряют историю изменений. В таких случаях лучше выполнять команду git pull --rebase, и Git сначала заберёт изменения из удалённой копии ветки, а потом накатит ваши незапушенные коммиты после самого последнего коммита удалённой ветки, так, как будто бы всё так и было. Таким образом необходимость мёржа отпадает и мы избежим уродливого коммита о мёрже в истории изменения.

7. Сохранение структуры ветки после локального мержа


Пример команды: git merge --no-ff

Мне нравится создавать новую ветку для каждого нового багфикса или фичи. Помимо прочего, это помогает чётко понимать, как серия коммитов связана с конкретным таском. Если вы когда-либо мёржили пул-реквесты на гитхабе или похожих сервисах, у вас всегда была возможность посмотреть подробную историю смёрженной ветки с помощью команды git log --oneline --graph.

Если вы когда-либо мёржили локальные ветки друг с другом, то вы могли заметить, что Git выравнивает историю коммитов, располагая их линейно один за другим. Для того, чтобы избежать этого и оставить историю изменений древовидной (такой, как после пул-реквеста), вы можете добавить параметр --no-ff, и в итоге получится подробная древовидная история: git merge --no-ff some-branch-name



8. Исправление последнего коммита вместо создания нового


Пример команды: git commit --amend

Этот совет очень простой. Допустим, вы сделали изменения, закоммитили их, а потом обнаружили опечатку. Вы можете сделать новый коммит с описанием «исправление опечатка», но есть вариант получше.

Если вы ещё не запушили изменения в удалённую ветку, то можно сделать вот так вот:
  1. Исправьте ошибку.
  2. Добавьте изменения в список файлов, подготовленных для коммита: git add some-fixed-file.js.
  3. Запустите команду git commit --amend, которая добавит эти последние изменения в самый последний коммит. Вы так же сможете отредактировать примечание к коммиту.
  4. Когда всё будет готово, вы можете запушить ветку в удалённый репозиторий.



Если вы работаете над веткой один, то вы можете исправить изменения даже после того, как вы запушили ветку в удалённый репозиторий. Просто выполните git push -f после внесения изменений, и удалённая ветка будет перезаписана. Но вам НЕ ЗАХОЧЕТСЯ делать это с веткой, с которой работают другие люди (как обсуждалось в совете про ребейз выше). В таких случаях, возможно, лучший вариант — новый коммит с примечанием «исправление опечатки».

9. Три состояния в Git и переключение между ними


Пример команды: git reset --hard HEAD и git status -s

Как вы, наверное, уже знаете, файл в Git может находится в одном из трёх состояний:
  1. unstaged — не добавлен в индекс для коммита
  2. staged — добавлен в индекс для коммита
  3. commited — закоммичен

(На самом деле есть ещё, как минимум, статус untracked — файл не добавлен в репозиторий — прим. перев.).

С помощью команды git status вы можете получить подробное описание файлов и их состояний. Чтобы добавить файл в индекс для коммита (перевести его из состояния unstaged в состояние staged), нужно выполнить команду git add filename.js. Команда git add . добавляет сразу все файлы (в текущей директории — прим. перев.).

Для более быстрого и простого просмотра состояния файлов можно воспользоваться командой git status -s, результат будет выглядеть примерно так:



Очевидно, что команда git status не покажет вам уже закоммиченные файлы, для их просмотра следует использовать команду git log.

Есть ещё несколько команд для переключения состояния файлов.

Сброс состояния файлов


Сброс позволяет откатиться на определённую версию в истории изменений Git. Всего есть три вида сброса:

  1. git reset --hard {{some-commit-hash}} — вернуться на определённый коммит в истории. Все изменения, сделанные после этого коммита пропадут.
  2. git reset {{some-commit-hash}} — вернуться на определённый коммит в истории. Все изменения, сделанные после этого коммита, получат состояние «Not staged for commit». Чтобы вернуть их обратно, нужно использовать команды git add и git commit.
  3. git reset --soft {{some-commit-hash}} — вернуться на определённый коммит в истории. Все изменения, сделанные после этого коммита, получат состояние «Staged for commit». Чтобы вернуть их обратно, нужно использовать команду git commit.

Поналачу эта информация может показаться бесполезной, однако, когда вы начнёте работать с разными версиями файлов, она вам очень пригодится. Например, я для себя выделил вот такие сценарии использования этих команд:

  1. Если я хочу отменить все внесённые изменения и начать работу с чистого листа, я использую команду git reset --hard HEAD (самый частый случай).
  2. Если я хочу отредактировать изменения и/или закоммитить файлы в другом порядке, я использу git reset {{some-start-point-hash}}.
  3. Если я просто хочу взять три последних коммита и слить их в один большой коммит, я использую команду git reset --soft {{some-start-point-hash}}.

Выгрузка отдельных файлов


если вам нужно отменить некоторые локальные изменения для конкретных файлов, но при этом изменения для других файлов трогать не нужно, гораздо проще забрать закоммиченные изменения этих файлов с помощью команды git checkout forget-my-changes.js. Это как git reset --hard, только для конкретного файла.

Также можно забирать разные версии файла из других коммитов или веток: git checkout some-branch-name file-name.js и git checkout {{some-commit-hash}} file-name.js.

Обратите внимание, что выгруженные файлы будут находиться в состоянии «Staged for commit», и чтобы убрать их из индекса для коммита нужно будет использовать команду git reset HEAD file-name.js. Для возврата в исходное состояние просто наберите git checkout file-name.js ещё раз.

Обратите внимание, что команда git reset --hard HEAD file-name.js не сработает. В целом процедура смены состояний в Git несколько запутана и не всегда можно сходу понять, что и как нужно сделать. Я надеюсь, что в этом совете я доступно и понятно всё объяснил.

10. Мягкая отмена коммитов


Пример команды: git revert -n

Это очень удобная команда на тот случай, если вам нужно откатить последние пару коммитов, покопаться в изменениях и найти проблемное место.

Обычный git revert автоматически закоммитит те изменения, которые откатили, запросив у вас примечание к новому коммиту отката. Флаг "-n" говорит гиту, чтобы тот не переживал по поводу срочного коммита новых изменений, ведь мы хотим просто посмотреть на них.

11. Просмотр диффов для всего проекта (а не по одному файлу за раз) с помощью сторонних инструментов


Пример команды: git difftool -d

Моя любимая программа для сравнения файлов в графическом интерфейсе — Meld. Я влюбился в неё ещё со времён Linux, и с тех пор она всегда со мной.

Я, кстати, не пытаюсь пропагандировать Meld. Скорее всего, у вас уже есть любимая программа для сравнения файлов, и, вероятнее всего, она умеет работать с Git, как для сравнения, так и для разрешения конфликтов. Просто запустите следующие команды, заменив «meld» на свою любимую утилиту:

git config --global diff.tool meld
git config --global merge.tool meld

После этого всё, что вам нужно — запустить команду git difftool some-file.js для просмотра изменений в соответствующей программе вместо консоли.

Однако некоторые программы (например, тот же meld) поддерживают сравнение не только файлов, но и директорий. Если вы запустите команду git difftool -d, то увидите дифф по всему проекту, и иногда это бывает полезно.



12. Игнорирование пробелов


Пример команды: git diff -w или git blame -w

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

К счастью, Git достаточно умён, чтобы понимать, что к чему. Вы можете запускать многие команды (такие, как git diff, git blame и т.д.) с флагом "-w" и Git будет просто игнорировать все изменения, связанные с пробельными символами (пробел, табуляция и другие).

13. Добавление определённых изменений из файла


Пример команды: git add -p

Кто-то из разработчиков Git явно неравнодушен к флагу "-p", поскольку обычно он добавляет очень удобные штуки к различным командам.

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



14. Поиск и удаление старых веток


Пример команды: git branch -a

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

Все ветки репозитория можно посмотреть с помощью команды git branch -a (ключ "-a" выведет все ветки, в том числе из удалённого репозитория), а параметр --merged покажет только те ветки, которые были вмёржены в ветку master.

Скорее всего, вы захотите сперва запустить команду git fetch -p (получить изменения из удалённого репозитория и удалить неактуальные данные), чтобы убедиться, что вы работаете с актуальными данными.



Можно изощриться и вывести списки всех удалённых веток, комментарии к последним коммитам в них и дату последних изменений:

git for-each-ref --sort=committerdate --format='%(refname:short) * %(authorname) * %(committerdate:relative)' refs/remotes/ | column -t -s '*'



К сожалению, насколько я знаю, нет более лёгкого способа получить список смёрженных веток. Так что здесь придётся сравнивать два списка или писать для этого скрипт.

15. Откладывание изменений определённых файлов


Пример команды: git stash —keep-index или git stash -p

Если вы ещё не знаете, что делает команда git stash, то она просто кладёт все незакоммиченные изменения в специальный стек Git. Потом вы в любой момент можете выполнить git stash pop и применить все эти изменения назад. Вы так же можете посмотреть список всех сохранённых состояний в стеке с помощью команды git stash list, для более подробной информации посмотрите справку: man git-stash.

У команды git stash есть один недостаток: она откладывает сразу все файлы, а иногда бывает удобно отложить только некоторые файлы, а остальные оставить и продолжить работать с ними.

Помните магический ключ "-p"? У команды git stash он тоже есть, и, как вы уже, наверное, догадались, этот при использовании этого ключа Git предложит вам выбрать те изменения, которые нужно отложить.

Обязательно попробуйте нажать "?", чтобы посмотреть список всех возможных опций.



Есть ещё один способ отложить только те файлы, которые нужно:

  1. С помощью команды git add добавьте в индекс для коммита те файлы, которые вы не хотите откладывать (например: git add file1.js, file2.js)
  2. Выполните git stash --keep-index, и Git отложит те файлы, которые не были добавлены в индекс для коммита
  3. Выполните git reset чтобы сбросить индекс и продолжите работу

16. Хорошие примечания к коммиту


По этой теме можно посоветовать замечательную статью «Как писать примечания к коммитам».

Здесь же я хочу особо подчеркнуть одно важное правило: хорошее примечание к коммиту должно заканчивать следующее предложение: «После применения данный коммит {{ текст вашего примечания }}». Например:

  • После применения данный коммит обновляет файл readme
  • После применения данный коммит добавляет валидацию вызова GET /user/:id API
  • После применения данный коммит отменяет коммит 12345

17. Автодополнения команд Git


Для некоторых операционных систем (например, Ubuntu), автодополнение для Git в шелле включено по умолчанию. Если в вашей операционной системе это не так (а в Mac OS X это не так), вы легко можете включить автодополнение.

18. Создание алиасов для часто используемых команд


TL;DR: используйте алиасы Git или bash для наиболее часто используемых команд Git.

Лучше всего работать с Git через командную строку. А лучший способ освоиться с командной строкой — каждый раз делать всё в полном объёме (набирать длинные команды). Однако рано или поздно приходит желание создать более короткие и удобные алиасы, чтобы не вводить каждый раз одни и те же команды.

Git имеет встроенную систему алиасов, например, если вы выполните следующую команду:

git config --global alias.l "log --oneline --graph"

Будет создан новый алиас для Git с названием "l", и это позволит вам набирать git l вместо git log --oneline --graph.

Примечание: к алиасам Git можно так же добавлять дополнительные параметры (например, git l --author="Alex").

Альтернатива алиасам гита — старые добрые алиасы Bash. Например, в моём конфиге .bashrc есть такая строка: alias gil="git log -online -graph", которая позволяет мне писать gil вместо полной длинной команды, и кроме того, это на целых два символа меньше, чем git l :).

19. Быстрый поиск плохого коммита


Пример команды: git bisect

Команда git bisect использует принцип «разделяй и властвуй» для поиска плохого коммита в большой истории изменений.

Представьте, что вы вернулись из продолжительного отпуска. Вы забираете последнюю версию проекта из удалённого репозитория и обнаруживаете, что та фича, над которой вы работали перед самым отпуском, теперь не работает. Вы проверяете последний сделанный вами коммит — там всё работает. Однако за время вашего отпуска в проекте появились сотни других коммитов, и вы представления не имеете, который из них оказался плохим и сломал вашу фичу.



Наверняка вы будете пытаться найти баг, поломавший вашу фичу и с помощью команды git blame на злополучном коммите найти человека, которому можно предъявить претензию. Но если баг трудно обнаружить, вы можете попробовать изучить историю изменений, в надежде отыскать плохой коммит.

Вот второй вариант — это именно тот случай, когда пригождается команда git bisect. Она позволяет вам найти плохой коммит в кратчайшие сроки.

Так что же делает git bisect?


После того, как вы укажете коммит, в котором ничего не работает («плохой» коммит) и коммит, в котором всё работает («хороший» коммит), git bisect разделит все коммиты, которые располагаются между ними пополам, переключится в новую (безымянную) ветку на этом срединном коммите и позволит вам проверить, работает ли в нём ваша фича.

Предположим, в этом «срединный» коммите всё работает. Тогда вы говорите об этом гиту с помощью команды git bisect good и у вас останется только половина всех коммитов для поиска того самого, поломавшего всё.

После выполнения этой команды Git разделит оставшиеся коммиты пополам и снова переключится в безымянную ветку на новом срединном коммите, позволив вам протестировать работоспособность вашей фичи. И так далее, пока вы не обнаружите тот самый «плохой» коммит.

Благодаря тому, что вы каждый раз делите группу коммитов пополам, для обнаружения искомого вам потребуется примерно log(n) итераций (см. Сложность алгоритма).

Список команд, которые понадобятся вам для работы с git bisect:

  1. git bisect start — начинаем процедуры разделения
  2. git bisect good {{some-commit-hash}} — помечаем коммит как хороший (например, последний сделанный вами перед отпуском)
  3. git bisect bad {{some-commit-hash}} — помечаем плохой коммит (например, HEAD главной ветки, HEAD означает последний коммит)
  4. В этот момент Git находит срединный коммит и сообщает вам, что можно проводить проверку
  5. git bisect bad — сообщаем Git о том, что наша фича не работает в проверяемом в данный момент коммите
  6. git bisect good — сообщаем Git о том, что наша фича работает в проверяемом в данный момент коммите
  7. Когда первый плохой коммит будет найден, Git сообщит вам об этом, при этом выполнение git bisect завершается
  8. git bisect reset — возвращаемся начальной точке процесса git bisect (например, к HEAD главной ветки)
  9. git bisect log — отображает последний успешно выполненный git bisect

Данную процедуру можно автоматизировать с помощью скрипта.



Перевёл Dreadatour, текст читал %username%.

Комментарии (60)


  1. rogrom
    24.09.2015 11:59
    +5

    хорошая шпаргалка, спасибо


  1. MaximAL
    24.09.2015 12:49
    +3

    Вы можете запускать многие команды (такие, как git diff, git blame и т.д.) с флагом "-w" и Git будет просто игнорировать все изменения, связанные с пробельными символами (пробел, табуляция и другие).
    Чёрт, для языка программирования Whitespace такая опция не подойдёт :-(


    1. Dreadatour
      24.09.2015 12:51

      Ой, опечатка. Там «команды», конечно же. Спасибо! Да, для whitespace эту опцию лучше не использовать :)


  1. amarao
    24.09.2015 12:57

    У меня есть давний вопрос по гиту, но никто на него не может толком ответить. Предположим, у нас есть какой-то коммит. И стопочка тегов. Хочется узнать, между какими двумя тегами находится коммит. Как?

    Тривиальная задача: понять, в какую версию попало изменение в первый раз. Но каждый раз сталкиваюсь с задачей — и каждый раз в ход идут какие-то ужасные хаки с перебором и т.д.


    1. Dreadatour
      24.09.2015 13:04
      +1

      А если как-нибудь вот так вот попробовать?

      git log {{commit-hash}}.. --reverse --oneline --decorate | grep 'tag: ' | head -1


      1. amarao
        24.09.2015 13:18

        Это я как раз и называю «хаки».


        1. Dreadatour
          24.09.2015 13:31
          +6

          Ну почему сразу «хаки», вполне себе рабочий вариант :) Много есть случаев, когда именно такой подход работает, а делать из гита монстра, который умет прям всё-всё-всё глупо и нет смысла. Linux-way, все дела.


    1. Myp
      24.09.2015 13:08
      +35

      git describe и git describe --contains ?


      1. Dreadatour
        24.09.2015 13:09

        Вот, так даже лучше :)


      1. amarao
        24.09.2015 13:17
        +6

        О, спасибо! (Не могу голосовать за карму, потому что у вас ни одного поста нету -_-).


    1. vovochka404
      25.09.2015 05:24
      +1

      git describe --abbrev=0 --tags Но похоже выше уже дали наводку :)


  1. yupic
    24.09.2015 13:20
    +2

    4. Просмотр ещё не влитых в родительскую ветку изменений

    Я обычно смотрю какие ветки не были влиты в master:
    git fetch origin
    git branch -a --no-merged origin/master
    


    1. Dreadatour
      24.09.2015 13:38

      Это совсем другой вопрос: 14. Поиск и удаление старых веток :)


      1. yupic
        24.09.2015 13:50
        +1

        Нет, это не старые ветки. Это ветки над которыми ведётся работа, но которые ещё почему-то не были влиты в master. Например, их отложили до следующего релиза или разработчика отвлекли другой, более срочной задачей.


        1. Dreadatour
          24.09.2015 13:51

          Оу оу оу, точно! Я что-то не так понял сходу. Конечно же вы правы!


  1. Envek
    24.09.2015 13:20
    +2

    По пункту 4: вместо git log --no-merges удобно использовать команду git cherry — она сравнивает коммиты по изменениям и, следовательно, «прокуривает» ситуацию, когда был сделан cherry-pick из одной ветки в другую.

    По пункту 11: Meld восхитителен — он гораздо лучше самого git'а понимает различные конфликтные ситуации и простые разруливает самостоятельно. В более сложных ситуациях он упрощает понимание того, что было сделано, так как показывает сразу три варианта — как было до того, как ветки разошлись, как стало в ветке А и как стало в ветке Б. Ну и всякие команды, которые быстро переносят результаты из веток в нужном порядке (типа «здесь берём оба варианта, но из ветки А идёт первым, а из Б — следом за ним»). Одна беда — на маке Meld неюзабелен.


    1. Dreadatour
      24.09.2015 13:33

      Под Mac OS X есть чудесный и волшебный Kaleidoscope.


    1. Biga
      24.09.2015 14:15

      Использую diffuse вместо meld. Он намного шустрее, но не умеет сравнивать директории. На маке вполне юзабелен, только надо что-то сделать с сочетаниями клавиш ctrl+стрелки, которые на маке привязаны на какие-то действия (можно перенастроить).

      Для мёржа настроил diffuse в четыре колонки:

      [merge]
          tool = diffuse_
      [mergetool "diffuse_"]
          cmd = diffuse \"$LOCAL\" \"$BASE\" \"$REMOTE\" \"$MERGED\"
      


    1. skobkin
      25.09.2015 06:46

      А под винду есть неплохой Winmerge.


      1. Dreadatour
        25.09.2015 11:30
        +1

        Под винду мне очень понравился Beyond Compare, правда я давно им пользовался, не знаю, что там в последних версиях.


        1. kloppspb
          25.09.2015 12:55

          В последних версиях всё хорошо, включая работу под Linux и Mac. Но при наличии бесплатных альтернатив выкладывать минимум $30 за персональную лицензию слишком обломно :)


          1. Dreadatour
            25.09.2015 15:38

            Согласен. Однако, зачем, например, покупать PyCharm при наличии бесплатных альтернатив (vim, например)? :)


            1. kloppspb
              25.09.2015 15:58

              Ну что ж сразу вим, всё зависит от задач и потребностей :)

              По мне так эклипсовского диффа вполне хватает. Одно время развлекался прикручиванием внешних утилит, перебирал всё, что описано здесь. Однозначно заменил meld на p4merge. Потом уже порывался заплатить за SmartSynchronize. А потом понял, что и половины всех возможностей всё равно не использую…


              1. VolCh
                29.09.2015 07:15

                То есть было бы меньше возможностей у тулзы — заплатили бы? :)


                1. kloppspb
                  29.09.2015 07:42

                  Странный подход. Зачем платить за то, что и так есть бесплатно? Вот если бы там было что-то, что позарез нужно, и чего нет в бесплатных аналогах — тогда другой вопрос.


                  1. macseem_327
                    01.10.2015 12:44

                    Вопрос в том, как скоро то, чего еще нет в бесплатных аналогах, но есть в платных, появится в этих бесплатных аналогах? :) Поэтому лично я, в этом контексте, не вижу смысла платить за это.


      1. vlivyur
        02.10.2015 15:30
        +1

        Он же только двухстороннее сравнение делает?


  1. Vooon
    24.09.2015 13:22
    +7

    Большую часть команд `git log` для меня заменил `tig`. habrahabr.ru/post/206606


    1. Dreadatour
      24.09.2015 13:35
      +3

      Tig очень хорош, да, но это всё-таки не совсем то, это GUI (ncurses).

      По поводу утилит есть ещё чумовая git up (и её же вариант на питоне, который лично я предпочитаю).


      1. borNfree
        29.09.2015 10:17

        Спасибо за `git up`, просто и полезно


  1. printercu
    24.09.2015 13:25
    +2

    Спасибо! Про reflog можно ещё немного написать бы, чтобы знали, что и такое бывает и иногда выручает.


    1. Dreadatour
      24.09.2015 13:36
      +2

      Про гит вообще книги писать можно было бы. Oh, wai~


  1. Dreyk
    24.09.2015 14:02
    +4

    По поводу текстов коммитов:
    Автор оригинала предлагает заканчивать фразу When applied this commit will... В статье, на которую он сам ссылается, предлагается писать императивом chris.beams.io/posts/git-commit/#imperative
    Для английского противоречий нет, так как форма When applied this commit will...Fix some bugs идентична императиву Fix some bugs

    В русском же эти формы разные. Не думаю, правда, что многие люди пишут коммит-месседжи на русском


    1. Envek
      25.09.2015 15:54
      +2

      Мы пишем на русском. У нас принята форма ответа на вопрос «Что было сделано?» (например: «Реализовано то-то» или «Исправлена ошибка такая-то»). Это удобно, когда прибегает менеджер и, бешено вращая глазами, требует срочно предоставить список, того, что мы сделали за неделю/месяц/в этом релизе. git log, минимальные правки, копипаст в письмо и можно программировать дальше.


  1. nitso
    24.09.2015 14:32
    +6

    Добавлю про --amend
    Бывает, что нужно исправить опечатку в комментарии к последнему коммиту, не затрагивая файлов. В этом случае удобно воспользоваться флагом allow-empty, чтобы гит не ругался на отсутствие изменений для коммита:

    git commit --amend --allow-empty -m "New commit message"
    



    1. isden
      25.09.2015 11:20
      +2

      Еще там удобно использовать "--no-edit", тогда используется коммент от коммита, и его не нужно писать заново.


    1. NickKolok
      30.09.2015 01:40
      +3

      У меня, кажется, без --allow-empty такая конструкция прокатывает.
      Просто вызываю стрелочкой вверх последний коммит из истории, правлю сообщение и в конец дописываю --amend.
      Пример:

      nickolaus@nicknout:~/CHAS-CORRECT/correct$ git add chrome/dictionary.js
      nickolaus@nicknout:~/CHAS-CORRECT/correct$ git commit -m "Bred"
      [master b145062] Bred
       1 file changed, 1 insertion(+), 1 deletion(-)
      nickolaus@nicknout:~/CHAS-CORRECT/correct$ git commit -m "В словарь" --amend
      [master b80d697] В словарь
       1 file changed, 1 insertion(+), 1 deletion(-)
      nickolaus@nicknout:~/CHAS-CORRECT/correct$ 
      
      


  1. Mistx
    24.09.2015 15:10

    «Хорошие примечания к коммиту»
    Я пишу комментарии в порядке добавления файлов, например, следующим образом:
    1. common/modules/delivery/components/classes/HTTPRequestAdapter: инициализация параметров curl перенесена в конструктор + отлажен механизм отправки
    2. common/modules/delivery/components/classes/cdek/CDEKAdapter: убран неиспользуемый метод форматирования даты

    Думаю такие комменты читать команде проекта, да мне самому удобнее


    1. f0rk
      24.09.2015 16:50
      +8

      Не совсем понял, обе строчки идут в один коммит? Если да, то это странно, суда по комментариям, это должны быть разные коммиты. А писать в коммите, какие файлы были затронуты — тоже странно, всегда можно посмотреть лог коммитов для конкретного файла с помощью git log, если хочется понять кто и зачем трогал файл.


  1. Kirpa
    24.09.2015 16:36
    +1

    «Если я хочу отменить все внесённые изменения и начать работу с чистого листа, я использую команду git reset --hard HEAD (самый частый случай).»

    Достаточно `git reset --hard`

    «Если я просто хочу взять три последних коммита и слить их в один большой коммит, я использую команду git reset --soft {{some-start-point-hash}}.»

    Лучше использовать `git rebase -i HEAD~3`. И про `reflog` можно добавить — обязательно пригодится, когда произойдёт неправильное использование `git rebase`


  1. JIghtuse
    24.09.2015 17:01
    +6

    Недавно узнал способ именовать stash, чтобы потом можно было по его описанию понять, что там такое:

    git stash save something something


  1. mediaton
    24.09.2015 21:04

    Не делайте ребейз коммитов, находящиеся вне вашего репозитория.

    Вот давно хотел спросить, что это значит? не делать rebase c удаленной ветки не своего репозитория?


    1. MuLLtiQ
      24.09.2015 22:46

      те кто себе сделают git pull «отребейзенной» (ну и словечко) ветки получат пачку конфликтов из-за того что их история коммитов начнет отличаться от той, которая будет лежать в репозитории


      1. mediaton
        24.09.2015 22:53

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


    1. side2k
      24.09.2015 22:47

      При ребейзе часть коммитов пересоздаётся — при этом ломается история.
      Т.е. «не делайте такого ребейза, который приведёт к изменению коммитов, сделанных не вами».


      1. grossws
        25.09.2015 00:32

        Точнее сказать, опубликованны. Т. к. свои опубликованные коммиты лучше не ребейзить, чтобы тот, кто уже коммитил что-то основанное на них, не страдал.


      1. Yeah
        25.09.2015 00:36
        +2

        Тоже с таким сталкивался. На самом деле особой проблемы нет.

        1. Всегда делайте только интерактивный rebase
        2. Внимательно изучайте список коммитов, который предлагается к применению
        3. Пропускайте (skip) те коммиты, которые делали не вы

        В этом случае проблем не будет.

        Предположим в мастере у вас 3 коммита:

        master:
        A---B---C
        

        Разработчик бранчуется от C и добавляет свои 2 коммита:

        feature:
        A---B---C
                           D---E
        

        Потом мы делаем rebase master HEAD~2. Страшно даже подумать, у-у-у-у… Что получаем?

        A---B'---C'
         B---C---D---E
        

        С точки зрения git коммиты B и B' разные, так как у них разные хэши. При попытке сделать git rebase master (без -i) git попытатеся применить коммит B на B' и C', что, естественно, вызовет конфликт. Однако разработчик должен четко понимать, что он с коммитами B и C не работал и они его вообще никак не волнуют. Потому при git rebase -i master он должен пропустить B и C.

        В результате после rebase разработчик получит:

        A---B'---C'
                             D---E
        


        1. ZyXI
          04.10.2015 01:15
          +2

          Вообще?то git при rebase автоматически пропускает уже применённые изменения, даже если они были применены в других (или как часть других) commit’ов. Конфликты иногда будут, если там ещё что?то рядом меняли, но rebase --interactive вам здесь поможет, только если вы прочитали все сделанные другими разработчиками изменения.

          Я с rebase работаю всегда в одном из двух режимов: histedit — меняю историю, но ничего никуда не переношу, имею для этого специальный alias: ri = !sh -c 'git rebase --interactive $(git merge-base --is-ancestor ${1:-master} ${2:-HEAD} && git rev-parse ${1:-master} || git merge-base ${1:-master} ${2:-HEAD})' -; или только перебазирование без каких?либо изменений истории. Первое используется чаще, особенно если соглашения, принятые в проекте, не требуют обязательный rebase на master. Именно histedit используется, чтобы не терять контекста, в котором были сделаны изменения.


    1. skobkin
      25.09.2015 06:43

      Не очень понятная формулировка. В прицнипе, это можно делать в общем репозитории в исключительных случаях.
      Например, если это будет делаться в ветке, которую ведёте только вы и это не затронет коммиты до момента ответвления. Правда, в данном случае надо не забывать пушить обновления только в эту ветку (git push -f origin your_branch), так как git может быть настроен на пуш всех веток, если не указано иного и вы перезапишете изменения коллег во всех остальных ветках.


  1. Yeah
    25.09.2015 00:21
    -1

    Добавлю от себя: всегда делайте squash перед слиянием ветки с master — это позволит избежать мусора в мастере. Как мы делаем у себя на проекте?

    1. Разработчик создает pull-request
    2. После получения достаточного количества голосов «за» разработчик делает git rebase -i HEAD~n, где n — это количество коммитов, которые он сделал в данной ветке (можно сказать, что это количество коммитов, на которое его ветка опережает master)
    3. Первый коммит отмечается pick, остальные — squash
    4. Все сообщения коммитов удаляются, первому выставляется подробное описание, что за фича, что сделал, что исправил
    5. git push -f
    6. merge
    7. ...
    8. PROFIT!

    В результате вместо такого
    image


    1. RusSuckOFF
      25.09.2015 01:09

      А если использовать arcanist + phabricator, то все это можно получить из коробки. Очень удобно, не говоря уж о удобстве phabricator-а для code review.


  1. isden
    25.09.2015 11:21

    Вопрос на засыпку. Чем принципиально отличаются unstaged и untracked? :)


    1. Yeah
      25.09.2015 11:51

      untracked — ни разу не был добавлен в git. git о таком файле ничего не знает, у него нет никакой истории изменений. Это, например, могут быть какие-либо автоматически сгенерированные файлы, логи и т.п.

      unstaged — файл уже добавлялся в git, сейчас он изменен, но не добавлен в будущий commit. Например, вы поменяли 5 файлов, но хотите сейчас закоммитить только 3, а оставшиеся 2 добавить в следующий commit. Вот те 2 и будут unstaged.


      1. isden
        25.09.2015 11:56

        Тут еще не хватает той картинки со staging area из мануала. Имхо, этот вопрос в статье стоило бы чуть подробнее пояснить, штука достаточно важная.


        1. JIghtuse
          25.09.2015 12:01
          +1

          Вы об этой?
          image
          (отсюда). Да, довольно наглядная.

          Только здесь вместо unstaged используется modified.


          1. isden
            25.09.2015 12:05

            Да, эта нагляднее. Я видел аналогичную, но чуть попроще, где-то в первой главе. Там было именно staging area.


  1. artbear
    26.09.2015 15:42
    +3

    Еще полезный совет:
    Если вы используете русскоязычные или любые другие не англоязычные имена файла при работе с Гит, для Вас будет полезным использование ключа -z
    Например, для просмотра новых файлов можно использовать команду

    git status --porcelain

    Для русскоязычного имени в этом случае будет показан результат
    ?? "\320\234\320\276\320\264\321\203\320\273\321\214.txt"

    а вот «правильная» команда
    git status --porcelain -z
    покажет намного более юзабельный вариант
    ?? Модуль.txt


    1. ZyXI
      04.10.2015 01:29

      Новый вариант совершенно не юзабельный, если изменения в нескольких файлах: например, вместо

      A  "\302\253\302\273"
      ??  .git /
      ?? a.img
      ?? ab/
      ?? abc.c
      ?? foo/
      показывается
      A  «»??  .git /?? a.img?? ab/?? abc.c?? foo/%
      (% — это от zsh, указывает на то, что вывод предыдущей команды не был завершён новой строкой). Может, ваш терминал воспринимает нулевой байт, как новую строку, но ни один из моих нет (и не должен). Если у вас есть такие файлы, то лучше иметь что?то вроде alias’а st = "!sh -c \"git status -z \\\"\\$@\\\" | tr '\\\\0' '\\\\n'\" -".


  1. habro_user
    01.10.2015 14:38
    +1

    Пожалуй, это лучшая из шпаргалок, что я читал по Git. Благодарю!


  1. Don_Eric
    11.10.2015 15:49
    -8

    SourceTree решает все проблемы