Оглавление

Git 1

Всякие первоначальные настройки репозитория



About Version Control

Система контроля версий (version control system, VCS) — это штука, которая запоминает изменения, сделанные в наборе файлов, запоминает, кто и когда сделал изменения, так что разные версии проекта можно было сравнить, вернуться к предыдущим версиям, или смерджить некоторые типы файлов.

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

Виды VCS:

Обычно VCS хранят изменения между файлами, а не сами файлы разных версий, чтобы не надо было хранить несколько одинаковых копий одного файла. Такое называется delta-based version control. А когда для каждой версии хранится отдельный полный набор фаелов (кроме тех, что повторяются полностью, повторяющиеся хранятся только в одном месте и следующие снапшоты просто ссылаются на них), то это называется snapshot-based version control.

Последовательность изменений можно представить в виде направленного ацикличного дерева: узлы — это отдельные revisions проекта, они упорядочены в порядке внесения изменений. Выделяется trunk или mainline дерева, который соответствует мастеру в гите, от него могут отходить ветки, которые потом могут сливаться с главной веткой. HEAD — это текущий конец главного ствола дерева. Baseline, tag, label — это всё одно и то же, это способ пометить какие-то отдельные снапшоты как наиболее важные (какой-то стабильный релиз или ещё что-то такое). Check out — создание рабочей копии, check in — наоборот, то же самое, что и коммит, внесение изменений.

Файлы в гите идентифицируются во SHA-1 хешу, с помощью него же обнаруживаются все внесённые изменения. Файлы в гит-проекте могут находиться в одном из трёх состояний (modified, staged, commited):



Git Basics

git clone. Я не знаю, почему, но у меня эта штука сначала, когда я использовал HTTPS URL, попросила авторизоваться с юзернеймом и паролем, и не дала склонировать чужой репозиторий. А когда я попробовал SSH URL, то всё спокойно склонировалось и с HTTPS URL проблем больше тоже не было. Так что, видимо, SSH URL — это предпочтительный вариант тут.

Эта команда тупо всё копирует, все снапшоты, всё-всё.

git clone git@github.com:username/repository.git


Помимо modified/staged/commited состояний файлы ещё могут быть tracked и untracked. Untracked — это фаелы, которые не были включены в последний коммит, то есть те, которые были созданы вот прямо сейчас. Tracked файлы — соответственно, фаелы, которые в последнем коммите есть. Из состояния untracked файлы сразу попадают в staged, после чего в commited.


Short status. Оказывается, в дурацком и громоздком git status есть флаг -s или --short, который выводит всё в более кратком виде:

git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

# ? - untracked
# A - added to the staging area (из untracked в staging area)
# M - modified
# R - renamed

При этом состояния файлов выводятся в два столбца: в первом — состояние файла в staging area, во втором — состояние в working directory. То естьMM означает, что файл изменили, добавили в некст коммит, а потом снова изменили. Или, например, AM — файл создали, добавили в коммит, а потом изменили.

Нормальный git diff. Кстати, оказывается, ещё помимо тупого неудобного git diff есть

git difftool [--staged] --tool=vimdiff

Который красиво всё выводит side-by-side в виме. А простой git diff всё выводит тупо и непонятно вообще. И я ещё не знал, что git diff выводит всё, что ещё не было staged, а git diff --staged наоборот — всё, что было staged.

Skipping staging area. Можно тупо добавить флаг -a при коммите, чтобы все изменения сами автоматически добавились. При этом он добавляет в коммит только те файлы, которые изначально были tracked, untracked файлы в коммит не попадают.



Ignoring Files

Ну, понятно, что это всё делается в .gitignore, но я не знал, что можно иметь дополнительные .gitignore в поддиректориях, которые переписывают правила, определённые в глобальном .gitignore в корне репозитория. А ещё можно использовать вот такие штуки:



Removing Files

Удаление вручную vs git rm. Если просто вручную удалить файл из working tree, то в следующем коммите он всё равно останется, нужно будет дополнительно после удаления сделать git rm %DELETED% или git add %DELETED%. И, я так понимаю, вообще так лучше не делать, потому что можно по ошибке что-то удалить и вернуть обратно будет нельзя.

git rm. Так что луче использовать git rm %FILENAME%, который добавляет факт удаления файла в следующий коммит и убирает его из working tree, если надо полностью удалить файл. Или же git rm --cached %FILENAME%, если надо оставить файл в working tree, а только удалить из БД (например, если забыл добавить какие-то файлы в gitignore и случайно закоммитил их).

Forced rm. Ещё один повод использовать эту штуку — это то, что он немного безопаснее, потому что не удаляет так просто файлы уже попавшие в staging area или просто изменённые, потому что все изменения будут потеряны. Нужно указать флаг -f, если хочешь удалить такие файлы.

Escaping glob patterns. Оказывается, вот эти штуки работают в том числе и в command line гит командах, но надо эскейпить звёздочки (или засовывать аргумент в кавычки), чтобы использовались glob patterns, определённые в гите, а не те, которые в командной строке. В большинстве случаев, правда, это не играет роли, но:



Moving (renaming) Files

Тут основное — это то, что если сделать mv %FROM% %TO%, то в git status он распишет это как удаление файла %FROM% и создание файла %TO%, так что нужно будет сделать вот это в итоге, чтобы переименовать файл:

mv FROM TO
git rm FROM
git add TO

Но при этом после всего этого при коммите гит, видимо, проверяет хеши файлов и определяет, что это один и тот же файл, который просто переместили, и помечает это всё как переименование.

Можно ещё явно сразу переместить с помощью одной команды вместо трёх: git mv FROM TO.



Viewing the Commit History

git log --oneline — просто отображает все коммиты в хронологическом порядке от последнего к самому первому, каждый коммит в одну строку (сокращённый хеш и commit message).

git log -p -<N> — для N последних коммитов отображает все измения, которые были сделаны (короче, просто diff для последних N коммитов). Ну и, видимо, для любых видов git log можно ограничить количество коммитов.

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

git log --pretty=format:"%h - %an, %ar : %s" — можно самому задать формат вывода коммитов. Там есть список всяких опций для вывода. Вроде как это для того, чтобы можно было вывестти всю эту информацию независимо от версии гита (может, дефолтный формат вывода мог поменяться) или просто, чтобы было легче распарсить это.

git log --oneline --graph — выводит историю коммитов с визуализацией веток. При этом он дополнительно всё немного упрощает: если параллельно с ответвлением в мастер не было коммитов, то это ответвление не будет отображено, всё будет выведено в одну линию.

Фильтры. Также там есть куча опций для того, чтобы отфильтровать вывод git log. Самое полезное — это опции для дат: --since/after/before/until=<date> и фильтр по наличию каких-то ключевых слов в commit messages: --grep/invert-grep=<pattern> (ещё один повод придерживаться каких-то соглашений при коммитах — чтобы потом можно было отфильтровать, скажем, по слову “add”). Ну и там ещё есть всякие опции, которые влияют на то, как будут обрабатываться паттерны. Ну, короче, там уже нет смысла без необходимости что-то понимать. Ну и ещё можно ограничить вывод коммитов только для какой-то директории, для этого просто в конце надо дописать git log ... -- <path>.

Git pickaxe. Ещё одна, видимо, полезная (потому что я не очень понимаю, как её на практике использовать) штука — это вывод тех коммитов, которые изменили то, насколько часто встречается какая-то строка в source файлах: git log -S <string>. Видимо, можно найти те коммиты, в которых добавился/убрался новый вызов какой-то функции. Хм, ну или можно определить, в какой момент была написана какая-то строка кода. Можно скомбинировать с флагом -p, чтобы ещё сразу увидеть код в контексте которого указанный кусок кода был добавлен/удалён.