24 мая 2009 в 22:54

Git Wizardry

Git*
1 Введение


В своей прошлой заметке я постарался осветить в общих чертах стиль работы с
распределенной системой контроля версий git и указать на отличия по сравнению с
классическими централизованными СКВ. Целью было прежде всего обобщение опыта
работы с системой без упоминания тонкостей синтаксиса отдельных команд.

Данный же топик задумывался как непосредственное введение в работу с git, нечто
среднее между tutorial и обобщенной справкой, до которого все же рекомендуется
прочитать упомянутое выше введение. Сознательно избегаются технические
подробности работы git, употребляются только общие для СКВ термины и
ограничивается список упоминаемых команд.


2 Работа с локальным репозитарием


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


2.1 Базовые команды


Базовые команды — те, без которых невозможно обойтись в разработке.


2.1.1 git init — создание репозитария


Команда git init создает в директории пустой репозитарий в виде директория
.git, где и будет в дальнейшем храниться вся информация об истории коммитов,
тегах — ходе разработки проекта:

mkdir project-dir

cd project-dir

git init

Другой способ создать репозитарий — команда git clone, но о ней чуть позже.


2.1.2 git add и git rm — индексация изменений


Следующее, что нужно знать — команда git add. Она позволяет внести в индекс — временное хранилище — изменения, которые затем войдут в коммит. Примеры
использования:

git add EDITEDFILE — индексация измененного файла, либо оповещение о
создании нового.

git add. — внести в индекс все изменения, включая новые файлы.

Из индекса и дерева одновременно проекта файл можно удалить командой git rm:

git rm FILE1 FILE2 — отдельные файлы

git rm Documentation/\*.txt — хороший пример удаления из документации к git,
удаляются сразу все файлы txt из папки.

Сбросить весь индекс или удалить из него изменения определенного файла можно
командой git reset:

git reset — сбросить нафиг весь индекс.

git reset — EDITEDFILE — удалить из индекса конкретный файл.

Команда git reset используется не только для сбрасывания индекса, поэтому дальше
ей будет уделено гораздо больше внимания.


2.1.3 git status — состояние проекта, измененные и не добавленные файлы, индексированные файлы


Команда git status, пожалуй, можно считать самой часто используемой наряду с
командами коммита и индексации. Она выводит информацию обо всех изменениях,
внесенных в дерево директорий проекта по сравнению с последним коммитом рабочей
ветки; отдельно выводятся внесенные в индекс и неиндексированные
файлы. Использовать ее крайне просто:

git status

Кроме того, git status указывает файлы с неразрешенными конфликтами слияния и
файлы, игнорируемые git.


2.1.4 git commit — совершение коммита


Коммиты — базовое понятие во всех системах контроля версий, поэтому совершатся
он должен легко и по возможности быстро. В самом своем простом виде достаточно
после индексации набрать:

git commit

Если индекс не пустой, то на его основе будет совершен коммит, после чего
пользователя попросят прокомментировать вносимые изменения вызовом команды
edit(например, в Ubuntu обычно вызывается простенький текстовый редактор nano, у
меня же — emacs). Сохраняемся, и вуала! Коммит готов.

Есть несколько ключей, упрощающих работу с git commit:

git commit -a — совершит коммит, автоматически индексируя изменения в файлах
проекта. Новые файлы при этом индексироваться не будут! Удаление же файлов
будет учтено.


git commit -m «commit comment» — комментируем коммит прямо из командной строки
вместо текстового редактора.

git commit FILENAME — внесет в индекс и создаст коммит на основе изменений
единственного файла.


2.1.5 git reset — возврат к определенному коммиту, откат изменений, «жесткий» или «мягкий»


Помимо работы с индексом (см. выше), git reset позволяет сбросить состояние
проекта до какого-либо коммита в истории. В git данное действие может быть двух
видов: «мягкого»(soft reset) и «жесткого» (hard reset).


«Мягкий» (с ключом "--soft") резет оставит нетронутыми ваши индекс и все дерево
файлов и директорий проекта, вернется к работе с указанным коммитом. Иными
словами, если вы обнаруживаете ошибку в только что совершенном коммите или
комментарии к нему, то легко можно исправить ситуацию:

  1. git commit… — некорректный коммит;

  2. git reset --soft HEAD^ — переходим к работе над уже совершенным коммитом,
    сохраняя все состояние проекта и проиндексированные файлы
  3. edit WRONGFILE

  4. edit ANOTHERWRONGFILE

  5. add.



6.1. git commit -c ORIG_HEAD — вернутся к последнему коммиту, будет предложено
редактировать его сообщение. Если сообщение оставить прежним, то
достаточно изменить регистр ключа -с:

6.2. git commit -C ORIG_HEAD

Обратите внимание на обозначение HEAD^, оно означает «обратиться к предку
последнего коммита». Подробней описан синтаксис такой относительной адресации
будет ниже, в разделе «Хэши, тэги, относительная адресация». Соответственно,
HEAD — ссылка на последний коммит. Ссылка ORIG_HEAD после «мягкого» резета
указывает на оригинальный коммит.


Естественно, можно вернуться и на большую глубину коммитов,

«Жесткий» резет (ключ --hard) — команда, которую следует использовать с
осторожностью. Git reset --hard вернет дерево проекта и индекс в состояние,
соответствующее указанному коммиту, удалив изменения последующих коммитов:

git add.

git commit -m «destined to death»

git reset --hard HEAD~1 — больше никто и никогда не увидит этот позорный коммит.

git reset --hard HEAD~3 — вернее, три последних коммита. Никто. Никогда.


Если команда достигнет точки ветвления, удаления коммита не произойдет.

Для команд слияния или выкачивания последних изменений с удаленного репозитария
примеры резета будут приведены в соответствующих разделах.


2.1.6 git revert — отмена изменений, произведенных в прошлом отдельным коммитом


Возможна ситуация, в которой требуется отменить изменения, внесенные отдельным
коммитом. Git revert создает новый коммит, накладывающий обратные изменения:


git revert config-modify-tag — отменяем коммит, помеченный тегом.

git revert 12abacd — отменяем коммит, используя его хэш.

Для использования команды необходимо, чтобы состояние проекта не отличалось от
состояния, зафиксированного последним коммитом.


2.1.7 git log — разнообразная информация о коммитах в целом, по отдельным файлам и различной глубины погружения в историю


Иногда требуется получить информацию об истории коммитов, коммитах, изменивших
отдельный файл; коммитах за определенный отрезок времени и так далее. Для этих
целей используется команда git log.

Простейший пример использования, в котором приводится короткая справка по всем
коммитам, коснувшимся активной в настоящий момент ветки (о ветках и ветвлении
подробно узнать можно ниже, в разделе «Ветвления и слияния»):

git log

Получить подробную информацию о каждом в виде патчей по файлам из коммитов
можно, добавив ключ -p (или -u):

git log -p

Статистика изменения файлов, вроде числа измененных файлов, внесенных в них
строк, удаленных файлов вызывается ключом --stat:


git log --stat

За информацию по созданиям, переименованиям и правам доступа файлов отвечает ключ
--summary:

git log --summary

Для исследования истории отдельного файла достаточно указать в виде параметра
его имя (кстати, в моей старой версии git этот способ не срабатывает,
обязательно добавлять " — " перед «README»):

git log README

или, если версия git не совсем свежая:

git log — README

Далее будет приводится только более современный вариант синтаксиса. Возможно
указывать время, начиная в определенного момента («weeks», «days», «hours», «s»
и так далее):

git log --since=«1 day 2 hours» README

git log --since=«2 hours» README

git log --since=«2 hours» dir/ — изменения, касающиеся отдельной папки.


Можно отталкиваться от тегов:

git log v1… — все коммиты, начиная с тега v1.

git log v1… README — все коммиты, включающие изменения файла README, начиная с
тега v1.

git log v1..v2 README — все коммиты, включающие изменения файла README, начиная с
тега v1 и заканчивая тегом v2.

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

Интересные возможности по формату вывода команды предоставляет ключ --pretty:


git log --pretty=oneline — выведет на каждый из коммитов по строчке, состоящей из хэша
(здесь — уникального идентификатора каждого коммита, подробней — дальше).

git log --pretty=short — лаконичная информация о коммитах, приводятся только
автор и комментарий

git log --pretty=full/fuller — более полная информация о коммитах, с именем
автора, комментарием, датой создания и внесения коммита

В принципе, формат вывода можно определить самостоятельно:

git log --pretty=format:'FORMAT'

Определение формата можно поискать в разделе по git log из Git Community Book
или справке. Красивый ASCII-граф коммитов выводится с использованием ключа
--graph.


2.1.8 git diff — отличия между деревьями проекта; коммитами; состоянием индекса и каким-либо коммитом.


Своего рода подмножеством команды git log можно считать команду git diff,
определяющую изменения между объектами в проекте: деревьями (файлов и
директорий):

git diff — покажет изменения, не внесенные в индекс.

git diff --cached — изменения, внесенные в индекс.


git diff HEAD — изменения в проекте по сравнению с последним коммитом

git diff HEAD^ — предпоследним коммитом

Можно сравнивать «головы» веток:

git diff master..experimental

Ну или активную ветку с какой-либо:

git diff experimental


2.1.9 git show — показать изменения, внесенные отдельным коммитом


Посмотреть изменения, внесенные любым коммитом в истории можно командой git
show:

git show COMMIT_TAG


2.1.10 git blame и git annotate — вспомогательные команды, помогающие отслеживать изменения файлов


При работе в команде часто требуется выяснить, кто именно написал конкретный
код. Удобно использовать команду git blame, выводящую построчную информацию о
последнем коммите, коснувшемся строки, имя автора и хэш коммита:

git blame README

Можно указать и конкретные строки для отображения:

git blame -L 2,+3 README — выведет информацию по трем строкам, начиная со второй.

Аналогично работает команда git annotate, выводящая и строки, и информацию о
коммитах, их коснувшихся:

git annotate README


2.1.11 git grep — поиск слов по проекту, состоянию проекта в прошлом


git grep, в целом, просто дублирует функционал знаменитой юниксовой
команды. Однако, он позволяет слова и их сочетания искать в прошлом проекта, что
бывает очень полезно:

git grep tst — поиск слова tst в проекте.

git grep -с tst — подсчитать число упоминаний tst в проекте.


git grep tst v1 — поиск в старой версии проекта.

Команда позволяет использовать логическое И и ИЛИ:

git grep -e 'first' --and -e 'another' — найти строки, где упоминаются и первое
слово, и второе.

git grep --all-match -e 'first' -e 'second' — найти строки, где встречается хотя
бы одно из слов.


2.2 Ветвление


Операции ветвления и слияния — сердце и душа git, именно эти возможности делают такой
удобной работу с системой.


2.2.1 git branch — создание, перечисление и удаление веток


Работа с ветками — очень легкая процедура в git, все необходимые механизмы
сконцентрированы в одной команде:

git branch — просто перечислит существующие ветки, отметив активную.

git branch new-branch — создаст новую ветку new-branch.

git branch -d new-branch — удалит ветку, если та была залита (merged) с
разрешением возможных конфликтов в текущую.

git branch -D new-branch — удалит ветку в любом случае.

git branch -m new-name-branch — переименует ветку.


git branch --contains v1.2 — покажет те ветки, среди предков которых есть
определенный коммит.


2.2.2 git checkout — переключение между ветками, извлечение отдельных файлов из истории коммитов


Команда git checkout позволяет переключаться между последними коммитами (если
упрощенно) веток:

checkout some-other-branch

checkout -b some-other-new-branch — создаст ветку, в которую и произойдет
переключение.

Если в текущей ветке были какие-то изменения по сравнению с последним коммитом в
ветке(HEAD), то команда откажется производить переключение, дабы не потерять
произведенную работу. Проигнорировать этот факт позволяет ключ -f:

checkout -f some-other-branch

В случае, когда изменения надо все же сохранить, используют ключ -m. Тогда команда
перед переключением попробует залить изменения в текущую ветку и, после
разрешения возможных конфликтов, переключиться в новую:

checkout -m some-other-branch


Вернуть файл (или просто вытащить из прошлого коммита) позволяет команда вида:

git checkout somefile — вернуть somefile к состоянию последнего коммита
git checkout HEAD~2 somefile — вернуть somefile к состоянию на два коммита назад по ветке.

2.2.3 git merge — слияние веток (разрешение возможных конфликтов).


Слияние веток, в отличие от обычной практики централизованных систем, в git
происходит практически каждый день. Естественно, что имеется удобный интерфейс к
популярной операции:

git merge new-feature — попробует объединить текующую ветку и ветку new-feature.

В случае возникновения конфликтов коммита не происходит, а по проблемным файлам
расставляются специальные метки а ля svn; сами же файлы отмечаются в индексе как
«не соединенные» (unmerged). До тех пор пока проблемы не будут решены, коммит совершить
будет нельзя.

Например, конфликт возник в файле TROUBLE, что можно увидеть в git status:

git merge experiment — произошла неудачная попытка слияния.

git status — смотрим на проблемные места.

edit TROUBLE — разрешаем проблемы.

git add. — индексируем наши изменения, тем самым снимая метки.

git commit — совершаем коммит слияния.

Вот и все, ничего сложного. Если в процессе разрешения вы передумали разрешать
конфликт, достаточно набрать:

git reset --hard HEAD — это вернет обе ветки в исходные состояния.


Если же коммит слияния был совершен, используем команду:

git reset --hard ORIG_HEAD


2.2.4 git rebase — построение ровной линии коммитов


Предположим, разработчик завел дополнительную ветку для разработки отдельной
возможности и совершил в ней несколько коммитов. Одновременно по какой-либо
причине в основной ветке также были совершены коммиты: например, в нее были
залиты изменения с удаленного сервера; либо сам разработчик совершал в ней
коммиты.

В принципе, можно обойтись обычным git merge. Но тогда усложняется сама линия
разработки, что бывает нежелательно в слишком больших проектах, где участвует
множество разработчиков.

Предположим, имеется две ветки, master и топик, в каждой из которых было совершенно несколько коммитов начиная с момента ветвления.
Команда git rebase берет коммиты из ветки topic и накладывает их на последний коммит ветки
master:

  1. git-rebase master topic — вариант, в котором явно указывается, что и куда
    прикладывается.
  2. git-rebase master — на master накладывается активная в настоящий момент
    ветка.



После использования команды история становится линейной. При возникновении
конфликтов при поочередном накладывании коммитов
работа команды будет останавливаться, а в проблемные местах файлов появятся
соответствующие метки. После редактирования — разрешения конфликтов — файлы
следует внести в индекс командой git add и продолжить наложение следующих
коммитов командой git rebase --continue. Альтернативными выходами будут команды
git rebase --skip (пропустить наложение коммита и перейти к следующему) или git
rebase --abort (отмена работы команды и всех внесенных изменений).

С ключом -i (--interactive) команда будет работать в интерактивном
режиме. Пользователю будет предоставлена возможность определить порядок внесения
изменений, автоматически будет вызывать редактор для разрешения конфликтов и так
далее.


2.2.5 git cherry-pick — применение к дереву проекта изменений, внесенных отдельным коммитом


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

git cherry-pick BUG_FIX_TAG — изменения, внесенные указанным коммитом будут
применены к дереву, автоматически проиндексированы и станут коммитом в активной
ветке.

git cherry-pick BUG_FIX_TAG -n — ключ "-n" показывает, что изменения надо
просто применить к дереву проекта без индексации и создания коммита.


2.3 Прочие команды и необходимые возможности


Для удобства работы с git было введено дополнительное понятие: тэг. Кроме того
дальше будет пояснена необходимость в хэшах, и его применение; показан способ
обращаться к коммитам при помощи относительной адресации.


2.3.1 Хэш — уникальная идентификация объектов


В git для идентификации любых объектов используется уникальный (то есть с
огромной вероятностью уникальный) хэш из 40 символов, который определяется
хэшируюшей функцией на основе содержимого объекта. Объекты — это все: коммиты,
файлы, тэги, деревья. Поскольку хэш уникален для содержимого, например, файла,
то и сравнивать такие файлы очень легко — достаточно просто сравнить две строки
в сорок символов.

Больше всего нас интересует тот факт, что хэши идентифицируют коммиты. В этом
смысле хэш — продвинутый аналог ревизий Subversion. Несколько примеров
использования хэшей в качестве способа адресации:

git diff f292ef5d2b2f6312bc45ae49c2dc14588eef8da2 — найти разницу текущего
состояния проекта и коммита за номером… Ну сами видите, каким.

git diff f292ef5 — то же самое, но оставляем только шесть первых символов. Git
поймет, о каком коммите идет речь, если не существует другого коммита с таким
началом хэша.

git diff f292 — иногда хватает и четырех символов.

git log febc32...f292 — читаем лог с коммита по коммит.


Разумеется, человеку пользоваться хэшами не так удобно, как машине, именно поэтому были
введены другие объекты — тэги.


2.3.2 git tag — тэги как способ пометить уникальный коммит


Тэг (tag) — это объект, связанный с коммитом; хранящий ссылку на сам коммит, имя
автора, собственное имя и некоторый комментарий. Кроме того, разработчик может
оставлять на таких тегах собственную цифровую подпись.

Кроме этого в git представленные так называемые «легковесные тэги» («lightweight
tags»), состоящие только из имени и ссылки на коммит. Такие тэги, как правило,
используются для упрощения навигации по дереву истории; создать их очень легко:

git tag stable-1 — создать «легковесный» тэг, связанный с последним
коммитом. Если тэг уже есть, то еще один создан не будет.

git tag stable-2 f292ef5 — пометить определенный коммит.

git tag -d stable-2 — удалить тег.

git tag -l — перечислить тэги.

git tag -f stable-1.1 — создать тэг для последнего коммита, заменить
существующий, если таковой уже был.

После создания тэга его имя можно использовать вместо хэша в любых командах
вроде git diff, git log и так далее:


git diff stable-1.1...stable-1

Обычные тэги имеет смысл использовать для приложения к коммиту какой-либо
информации, вроде номера версии и комментария к нему. Иными словами, если в
комментарии к коммиту пишешь «исправил такой-то баг», то в комментарии к тэгу по
имени «v1.0» будет что-то вроде «стабильная версия, готовая к использованию»:

git tag -a stable — создать обычный тэг для последнего коммита; будет вызван
текстовый редактор для составления комментария.

git tag -a stable -m «production version» — создать обычный тэг, сразу указав в
качестве аргумента комментарий.

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


2.3.3 Относительная адресация


Вместо ревизий и тэгов в качестве имени коммита можно опираться на еще один
механизм — относительную адресацию. Например, можно обратиться прямо к предку
последнего коммита ветки master:

git diff master^

Если после «птички» поставить цифру, то можно адресоваться по нескольким предкам
коммитов слияния:

git diff HEAD^2 — найти изменения по сравнению со вторым предком последнего
коммита в master. HEAD здесь — указатель на последний коммит активной ветки.

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

git diff master^^ — что привнес «дедушка» нынешнего коммита.

git diff master~2 — то же самое.

Обозначения можно объединять, чтобы добраться до нужного коммита:

git diff master~3^~2

git diff master~6


2.3.4 файл .gitignore — объясняем git, какие файлы следует игнорировать


Иногда по директориям проекта встречаются файлы, которые не хочется постоянно
видеть в сводке git status. Например, вспомогательные файлы текстовых
редакторов, временные файлы и прочий мусор.

Заставить git status игнорировать можно, создав в корне или глубже по дереву
(если ограничения должны быть только в определенных директория) файл
.gitignore. В этих файлах можно описывать шаблоны игнорируемых файлов
определенного формата.

Пример содержимого такого файла:

>>>>>>>Начало файла

#комментарий к файлу .gitignore

#игнорируем сам .gitignore


.gitignore

#все html-файлы…


*.html


#… кроме определенного


!special.html


#не нужны объектники и архивы

*.[ao]

>>>>>>>>Конец файла

Существуют и другие способы указания игнорируемых файлов, о которых можно узнать
из справки git help gitignore.


3 «Вместе мы — сила», или основы работы с удаленным репозитарием


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


3.1 Удаленные ветки (remote tracking branches)


Новое понятие здесь — удаленные ветки. Удаленные ветки соответствуют какой-либо
ветке (чаще master) на удаленном сервере. Одна такая создается автоматически при
создании копии удаленного репозитария; все команды, связанные с удаленной
работой, будут по умолчанию использовать именно эту удаленную ветку (обычно
называется «origin»).

Рассмотрим эти команды.


3.2 git clone — создание копии (удаленного) репозитария


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

git clone /home/username/project myrepo — клонируем репозитарий с той же машины
в директорию myrepo.

git clone ssh://user@somehost:port/~user/repository — клонируем репозитарий,
используя безопасный протокол ssh (для чего требуется завести у себя на машине
эккаунт ssh).

git clone git://user@somehost:port/~user/repository/project.git/ — у git имеется
и собственный протокол.


3.3 git fetch и git pull — забираем изменения из центрального репозитария (из удаленной ветки)


Для синхронизации текущей ветки с репозитарием используются команды git fetch и
git pull.

git fetch — забрать изменения удаленной ветки из репозитария по умолчания,
основной ветки; той, которая была использована при клонировании
репозитария. Изменения обновят удаленную ветку (remote tracking branch), после
чего надо будет провести слияние с локальной ветку командой git merge.

git fetch /home/username/project — забрать изменения из определенного
репозитария.

Возможно также использовать синонимы для адресов, создаваемые командой git remote:

git remote add username-project /home/username/project

git fetch username-project — забрать изменения по адресу, определяемому
синонимом.

Естественно, что после оценки изменений, например, командой git diff, из надо
создать коммит слияния с основной:

git merge username-project/master

Команда git pull сразу забирает изменения и проводит слияние с активной веткой:

git pull — забрать из репозитария, для которого были созданы удаленные ветки по
умолчанию.

git pull username-project — забрать изменения из определенного репозитария.


Как правило, используется сразу команда git pull.


3.4 git push — вносим изменения в удаленный репозитарий (удаленную ветку)


После проведения работы в экспериментальной ветке, слияния с основной,
необходимо обновить удаленный репозитарий (удаленную ветку). Для этого
используется команда git push:

git push — отправить свои изменения в удаленную ветку, созданную при
клонировании по умолчанию.

git push ssh://yourserver.com/~you/proj.git master:experimental — отправить изменения
из ветки master в ветку experimental удаленного репозитария.

git push origin :experimental — в удаленном репозитарии origin удалить ветку experimental.

git push origin master:master — в удаленную ветку master репозитария origin (синоним
репозитария по умолчанию) ветки локальной ветки master.


4 git-о-день


В этом разделе будут показаны и разобраны подробно несколько обычных и чуть
меньше необычных для работы с git ситуаций.


4.1 Обычный workflow при работе с локальным репозитарием


Git обладает необычайной легкостью в использовании не только как распределенная
система контроля версий, но и в работе с локальными проектами. Давайте разберем
обычный цикл — начиная с создания репозитария — работы разработчика git над
собственным персональным проектом:

  1. mkdir git-demo

  2. cd git-demo

  3. git init

  4. git add.

  5. git commit -m «initial commit»

  6. git branch new-feature

  7. git checkout new-feature

  8. git add.

  9. git commit -m «Done with the new feature»

  10. git checkout master

  11. git diff HEAD new-feature

  12. git merge new-feature

  13. git branch -d new-feature

  14. git log --since=«1 day»



Разберем каждое из действий. 1-2 — просто создаем рабочую директорию
проекта. 3 — создаем репозитарий в директории. 4 — индексируем все существующие
файлы проекта (если, конечно, они вообще были). 5 — создаем инициализирующий
коммит. 6 — новая ветка, 7 — переключение в нее (можно сделать в один шаг
командой git checkout -b new-feature). Далее, после непосредственной работы с
кодом, индексируем внесенные изменения(8), совершаем коммит(9). Переключаемся в
основную ветку(10), смотрим отличия между последним коммитом активной ветки и
последним коммитом экспериментальной (11). Проводим слияние (12) и, если не было
никаких конфликтов, удаляем ненужную больше ветку (13). Ну и на всякий случай
оценим проведенную за последний день работу (14).

Почему именно так? Зачем отказываться от линейной модели? Хотя бы даже потому,
что у программиста появляется дополнительная гибкость: он может переключаться
между задачами (ветками); под рукой всегда остается «чистовик» — ветка
master; коммиты становятся мельче и точнее.


4.2 Workflow при работе с удаленным репозитарием


Предположим, что вы и несколько ваших напарников создали общественный
репозитарий, чтобы заняться неким общим проектом. Как выглядит самая
распространенная для git модель общей работы?

  1. git clone http://yourserver.com/~you/proj.git
    … возможно, прошло некоторое время.

  2. git pull

  3. git diff HEAD^

  4. git checkout -b bad-feature
    … работаем некоторое время.

  5. git commit -a -m «Created a bad feature»

  6. git checkout master

  7. git pull

  8. git merge bad-feature

  9. git commit -a

  10. git diff HEAD^
    … запускаем тесты проекта, обнаруживаем, что где-то произошла ошибка. Упс.


  11. git reset --hard ORIG_HEAD

  12. git checkout bad-feature
    … исправляем ошибку.

  13. git -m bad-feature good-feature

  14. git commit -a -m «Better feature»

  15. git checkout master

  16. git pull

  17. git merge good-feature

  18. git push

  19. git branch -d good-feature



Итак, первым делом создаем (1) создаем копию удаленного репозитария (по
умолчанию команды вроде git pull и git push будут работать с ним). «Вытягиваем»
последние обновления (2); смотрим, что же изменилось(3); создаем новую ветвь и
переключаемся в нее (4); индексируем все изменения и одновременно создаем из них
коммит (5); переключаемся в главную ветвь (6), обновляем ее (7); проводим
слияние с веткой bad-feature(8) и, обнаружив и разрешив конфликт, делаем коммит
слияния (9).

После совершения коммита отслеживаем изменения(10), запускаем, например,
юнит-тесты и с ужасом обнаруживаем, что после слияния проект валится на большей
части тестов.

В принципе, тесты можно было прогнать и до коммита, в момент
слияния (между пунктами 8 и 9); тогда бы хватило «мягкого» резета.

Таким образом, приходится совершить «жесткий» (11) сброс произошедшего слияния,
ветки вернулись в исходное до состояние. После чего переключаемся в неудачную
ветку (12), вносим необходимые изменения и переименовываем ветку (13). Совершаем
коммит (14); переходим в главную ветку(15), опять ее обновляем (16). На этот раз
бесконфликтно делаем слияние (17), закидываем изменения в удаленный репозитарий
(18) и удаляем ненужную теперь ветку (19). Закрываем ноутбук, одеваемся и идем
домой под утро.


5 Заключение


В топике не рассмотрено несколько важных вопросов, вроде администрирования
общественного репозитария, интеграции с текстовыми редакторами или IDE,
использования SSH под Линукс и Windows; приветствуются замечания, и особенно
дополнения в раздел «git-о-день».


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

Это все я сейчас собираю в отдельный топик, которую и выкину сюда через неделю-полторы.
+43
238366
1201
VlK 94,7
Похожие публикации
Дайджест интересных материалов из мира веб-разработки и IT за последнюю неделю №134 (10 — 16 ноября 2014) 16 ноября в 23:05
Приглашаем на конференцию по web-разработке 29 ноября 14 ноября в 16:46
Создание User-Friendly движка бизнес-процессов на основе Windows Workflow Foundation 13 ноября в 10:55
Разработка программы в Multimedia Builder на примере утилиты для удаленной работы с кассовым ПО 13 ноября в 10:11
.NET Server Core, кросс-платформенная разработка, Visual Studio 2015 и другие анонсы Microsoft Connect() 12 ноября в 21:45
Дайджест интересных материалов из мира веб-разработки и IT за последнюю неделю №133 (3 — 9 ноября 2014) 9 ноября в 23:14
Подробное описание возможностей разработки с Microsoft Azure Cloud Services 7 ноября в 12:27
Дайджест интересных материалов из мира веб-разработки и IT за последнюю неделю №132 (27 октября — 2 ноября 2014) 2 ноября в 23:43
Деплой сайта через Git: удобная и простая альтернатива rsync и ftp 7 мая 2013 в 09:42
Git Workflow 20 мая 2009 в 13:37

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

–9
mapcoxog #
Может быть стоит сделать кат? )
+13
maxshopen #
Может быть стоит всё-таки это предложение написать автору лично через почту?
+10
Yeah #
Автор, я сломал скролл!
0
Nichola #
Так рвался к коментам :)
–1
A2K #
редкий случай когда при чтении поста в гуглоридере хочется кого-то убить…
0
VlK #
Я случайно вместо предпросмотра хлопнул «опубликовать». Круто :)
0
satori #
23 экрана!) рекордсмен))
–3
VlK #
В общем так. У меня нормальная версия этого безобразия, в html, с красивым оглавлением и вообще более симпатичная. Кому надо — пишите лично, вышлю на почту. На хабре такие вещи фиг опубликуешь!
0
mOlind #
ну так может выкладывай куда-нить. (на google pages например) а на хабр просто линк на статью.
–9
potap #
хабракат если вас не затруднит*
–2
damirych #
Хорошо, что прошлую заметку (о которой идет речь в самом начале поста) не стали квотить :)
0
Jenyay #
Спасибо. После Вашей предыдущей статьи поигрался с git, появились некоторые вопросы, но эта статья большинство вопросов сняла. Остались вот такие:

1. Пусть у нас есть две ветки master и test, в каждой ветке произошли изменения (без конфликтов). Если из мастера я делаю git merge test, то изменения из test перебираются в master. А при этом что изменения из master не добавляются в test?

2. В конфиге я сделал, чтобы кодировка для текста коммитов была Win1251, сделал git clone, а там эта настройка не сохранилась. Можно ли как-нибудь клонировать вместе с настройками?

3. При клонировании файл gitingnore тоже не сохранится в клоне?
+1
VlK #
1. Нет, изменения накладываются только на ту ветку, из который был сделан merge. Ветку test, в принципе, можно удалять после завершения работы.

2. Какой именно конфиг?

3. Вообще .gitignore можно добавить в репозитарий, как и любой другой файл в дереве. Однако, он чаще всего уникальный для каждого из клонов, либо на сервере лежит просто начальный шаблон, который каждый затем подстраивает под себя…

Дело в том, что каждая IDE, и некоторые текстовые редакторы, оставляет после себя уникальную кучу мусора, которую не следует пихать в проект. Вы используете Эклипс, я — Емакс, кто-то третий — еще что. Соответственно, каждый должен самостоятельно подрегулировать .gitignore.

Кроме файла .gitignore есть еще где-то в директории .git файлик exclude, общий для проекта; он читается во вторую очередь, имеет тот же формат. По идее, git clone должен забирать эту настройку, но я не уверен и и не могу сейчас проверить, гляньте сами и, если есть возможность, напишите сюда, буду благодарен. :)
0
Jenyay #
1. Спасибо, понятно.

2. В папке .git есть файл config. Туда я добавлял строки

[i18n]
commitencoding = Windows-1251

После клонирования в config в новом репозитории этих строк не было.

3. Да, я как раз добавлял маски в .git/info/exclude. Сейчас попробовал, при клонировании из него тоже пропали все маски.
0
VlK #
К сожалению, с ходу не нашел способа решить эту проблему. Если Вы решите ее — поделитесь? :)
0
Jenyay #
Обязательно :)
+1
mou #
Я бы посоветовал использовать UTF-8 в качестве кодировки. Это облегчит будущее, когда Redmond наконец признает дуальность Windows слишком долгоживущей проблемой. Совет не «с потолка», а практика из жизни.
0
Jenyay #
Да я и рад был бы использовать UTF-8, только когда вводишь комментарии к коммиту из консоли, текст получается именно в кодировке Win1251.
0
vovkab #
можно использовать cygwin и в качестве консоли к нему puttycyg. Это намного удобней чем стандартный cmd
0
Jenyay #
Надо будет попробовать. Я сейчас активно ищу какую-нибудь замену виндовой консоли, но ничего путного пока не попалось.
0
VlK #
Cygwin — это не просто консоль, это вроде среды Юникс под Windows. Там будут все утилиты, характерные для Линукса и компании. Наслаждайтесь :))
0
Jenyay #
Уже посмотрел. Как раз из-за того, что там не просто консоль, мне оно и не подходит. В нем не удается скакать по папкам на разных дисках типа cd c:\bla-bla-bla. Да и с русскими буквами проблемы (хотя возможно это настраивается).

Вот если бы putty заставить работать как обычную виндовую консоль, то было бы неплохо.
0
vovkab #
отчего же? Все диски лежат здесь: /cygdrive/
например диск С будет cd /cygdrive/c,

Для удобства можно сделать симлинк ln -s /cygdrive/c /c
теперь что бы попасть на диск C делаем cd /c

Собственно, если будете ставить cygwin, то и git можно поставить через него. И самое главное, это огромное количество мелких утилит, без которых в винде очень тяжко и неудобно.
0
Jenyay #
Так с путями как-то неудобно работать, потому что я их обычно копирую из FreeCommander-а, а здесь их придется еще изменять. Сейчас разбираюсь с Этой прогой, скорее dcuj она мне больше подойдет.

А утилиты да, вещь полезная.
+1
vovkab #
Что бы не копировать пути, а открывать консоль в нужной папке по правому клику, нашел инструкцию как добавить в контекстное меню:
goingspare.wordpress.com/2007/03/29/adding-open-with-console-to-the-context-menu/
0
Jenyay #
О, спасибо, попробую.
0
mou #
Я сам не пробовал (не использую «окна») но посмотрите на PowerShell
0
mou #
У Cmd есть параметр запускающий ее в Unicode режиме
0
Jenyay #
Хм, не знаю, спасиб. Посмотрю.
0
Jenyay #
В смысле не знал
0
arestov #
Огромное спасибо за труд!
+1
Bytamine #
Сделайте шпаргалку, как vim quick reference card :)
0
VlK #
Она так и задумывалась. Будь здесь возможность сделать оглавление…
+1
Drinker #
Я иногда ещё пользуюсь

git diff --name-only branch

Получая список изменённых файлов. Подобный ход незаменим, когда разработка ведётся у нас, а у клиента сервер, где не даётся доступ по ssh и есть только ftp (поэтому ни git ни rsync недоступны)
0
mou #
Спасибо огромное. Очень толково и без излишних «сюсюканей» свойственных большинству novice guide.
0
f1vlad #
Спасибо, некоторые новые команды узнал.
0
InneR #
Есть чудесная вещица на английском, GIT Magic
Как по мне, в отличие от топика, там приятнее язык и подача материала.
0
vovkab #
Дествительно чудесная вещица это peepcode.com/products/git, правда стоит денег. Но там очень все хорошо расказывается и есть видео как пользоваться. Так же есть удобный cheetsheet
0
VlK #
я учился по Git Community Book, там тоже есть скринкасты и очень разжеван материал; потом уже по отдельным howto и справке к командам и руководству к самому git.

0
mkechinov #
А я до конца дочитал. Буду пробовать.
0
ptiss #
в 4.2.6. не git checkout ли должно быть?
0
VlK #
ага, спасибо, очепятался.
0
ptiss #
вот, теперь в закладки и давать читать неверующим.
0
gkirok #
за описание настройки и администрирования общественного репозитария был бы очень признателен
+1
VlK #
Мне в ближайшую неделю, если не дни, как раз надо будет поднимать на локальный офис и нескольких удаленных работников репозитарий с довольно сложной настройкой по правам доступа. Думаю, к концу следующей недели будет практический материал и время.
0
gkirok #
спасибо ждем
0
klen #
Подскажите как в git проекте добавить вложенный git проект, допустим в уже существующем проекте должна быть папка bin(например) с файлами из другого git проекта.

Как это настроить по уму, нигде не могу откопать. Нужно, чтобы при клонировании основного GIT проекта, в нем появлялась папка bin и чтобы можно было централизованно обновлять и основной проект и вложенные папки с другим проектом. В svn сделал бы через svn:externals. Как сделать в GIT?
0
VlK #
Для этого используется Git Submodules; однако, сам в этой фишке не разбирался и на данный момент надобности и времени нет. Обещать освещение в следующей заметке, к сожалению, не могу.
0
ivanych #
1. Правильно ли я понял, что перед каждым коммитом нужно делать «git add .»? Зачем? Вот в SVN, например, этого делать не надо, SVN сам знает, какие файлы изменились.

2. В главе «4.1 Обычный workflow...» в пункте «12. git merge» не указано, с чем мержить. Это опечатка, или так и должно быть и git как-то сам догадывается, с чем мержить?

3. Делаем «git clone» с оригинального репозитория, что-то там правим и фиксируем коммит. Потом «git branch new», потом «git checkout new», потом изменяем в ветке new какие-то файлы и фиксируем коммит. Теперь, не переключаясь на master и не делая merge, сразу делаем «git push». Что и куда пойдет, какие изменения окажутся в оригинальном репозитории?
0
VlK #
1. можно просто написать git commit -a -m «commit comment», тогда перед коммитом автоматически внесутся в индекс изменения в известных файлах. git add. добавит и измененные известные, и неизвестные системе файлы.

2. Очепятка, уже исправил. Веток, естественно, может быть много, надо указывать, что заливать в ветку.

3. Ничто и никуда не пойдет, если только не указывать явно в параметрах команды git push.

Удаленная ветка ставится в данном случае в соответствие master, и именно оттуда можно сделать git push. Удаленные ветки можно посмотреть командой git branch -r, либо заглянуть в .git/config — там очень простой синтаксис конфигурации, видно, что и чему ставится в соответствие.
0
ivanych #
1. Что такое «индекс»?

2. «git reset — сбросить нафиг весь индекс» — что делает эта команда? Попробовал — не вижу никакого эффекта. Что должно произойти?

3. Как откатиться, не трогая репозиторий? Команда «git reset --[soft|hard]» изменяет репозиторий (или я что-то не так понял), а как изменить сами файлы, привести их к состоянию на момент какого-то коммита, не изменяя при этом репозиторий?
+1
VlK #
1. Индекс — сущность, в которой фиксируются изменения, предназначенные для коммита.

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

git reset --soft — этой командой мы откатываемся до какого-либо коммита, делая его «текущим»; само дерево проекта останется в исходном состоянии, а индекс сохранит все изменения, внесенные после «текущего» коммита. Иными словами, файлы не меняются, а индекс вмещает все изменения. Можно таким образом поправить коммит:

Сама по себе git reset просто очистит индекс от изменений. Вы добавили какие-то файлы, но ошиблись (например, файлы логов или левые картинки); эта команда отменяет это действиею.

3. Вернуть файл (или просто вытащить из прошлого коммита) позволяет команда git checkout (она же переключает нас между ветками):

git checkout somefile — вернуть somefile к состоянию последнего коммита
git checkout HEAD~2 somefile — вернуть somefile к состоянию на два коммита назад по ветке.

Естественно, репозитарий при этой не меняется.
0
ivanych #
Т.е. «git reset» просто сбросит добавленные файлы, и все? Изменения в файлах он же не отменит?
0
VlK #
Именно. Для этого надо использовать git reset --hard.
0
ivanych #
Пункт 3 неплохо бы добавить в саму статью, а?
0
ivanych #
Делаю git clone — репозиторий клонируется, файлы появляются, все нормально.

Дальше делаю git pull — а оно мне сообщает:

— You asked me to pull without telling me which branch you
want to merge with, and 'branch..merge' in
your configuration file does not tell me either. Please
name which branch you want to merge on the command line and
try again (e.g. 'git pull ').
See git-pull(1) for details on the refspec.

If you often merge with the same branch, you may want to
configure the following variables in your configuration
file:

branch..remote = branch..merge = <remote-ref>
remote..url = remote..fetch = See git-config(1) for details.
— Что оно от меня хочет? Какие такие сомнения его терзают? И так же вроде понятно, что с чем мержить — исходный репозиторий с активной локальной веткой.
0
ivanych #
Блин, форматирование слетело. Но смысл понятен.
0
ivanych #
Так, отвечаю сам себе.

Перед тем, как клонировать исходный репозиторий, нужно в нем перейти в ветку master. В этом случае при клонировании в новый репозиторий будет скопирована именно ветка master.

Если в исходном репозитории не выбрать никакую ветку, то при клонировании в новый репозиторий будет скопировано нечто, под названием «no branch».

Содержимое этого нечто будет, вроде, идентично ветке master, но не будет связано ни с master, ни с чем-то еще, поэтому сделать далее «git pull» не получится, вылезет приведенное выше сообщение об ошибке с требованием явно указать, какую ветку с какой мержить.
0
VlK #
У меня сейчас завал на работе, на эксперименты и продолжение серии топиков времени, к огромному моему сожалению, пока нет. Зато будет любопытный опыт практического внедрения git в проекте.

Интересная ситуация. А как вообще вышло так, что «в исходном репозитории не выбрать никакую ветку»?

дополнение про checkout сейчас внесу в топик.
0
ivanych #
Ну, я сделал исходный репозиторий:

git init
git add.
git commit -am 'start'

и все, больше ничего не делал. При этом, я так понимаю, ветка master автоматически выбрана не была.

Если бы это был рабочий, действующий репозиторий, то, вероятно, кто-нибудь в какой-нибудь момент выбрал бы ветку master и дальше, при клонировании, все прошло бы нормально. А я ничего реального в репозитории не делал, это просто был тест и ветку master я специально не выбирал.

0
ivanych #
1) После того, как в локальном репозитории сделали «git push», как получить эти изменения в удаленном репозитории?

В удаленном репозитории я делаю «git log» и вижу коммит с нужными изменениями, команда «git status» показывает, что имеются модифицированные файлы, но сами файлы при этом остаются старыми. Как их обновить? В SVN я бы сделал «svn update», а тут что нужно сделать?

2. В главе «4.2 Workflow при работе с удаленным репозитарием » пункт «6. git branch master» — не опечатка ли? Не должен ли там быть checkout вместо branch?

3. Надеюсь, я Вас еще не достал? :)
0
VlK #
Прошу прощения за задержку.

Опечатку исправил.

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

Работа, как обычно, ведется у себя, потом результат закидывается в «голый», и оттуда уже раздается по коллегам или, скажем, себе домой.

Вообще интересно, надо бы почитать.
0
ivanych #
>Работа, как обычно, ведется у себя, потом результат закидывается в «голый», и оттуда уже раздается по коллегам или, скажем, себе домой.

Вы же сначала сказали открытый для чтения?

И вот, скажем, нужна «точка», из которой результат работы нужно показывать заказчику. Нельзя же показывать из личного репозитория разработчика — там же в каждый конкретный момент времени может быть что угодно. Вот я и думаю показывать из центрального репозитория, где всегда будет актуальная работоспособная версия, а для этого нужно туда пушить и после пуша файлы в центральном требуется обновить.
0
VlK #
И нет, не достали :) Я человек любопытный, вы человек любопытный, почему бы не узнать что-нибудь новенькое? :)
0
vreznikov #
По первому вопросу, после push на удаленном репозитарии надо выполнить checkout -f
0
maxmaximov #
В мемориз, однозначно! ) Спасибо за чудесный мануал.

Для удобства работы с git было введено дополнительное понятие: тэг.

В CVS'е же тоже теги есть. И в SVN'е (хотя, технически они ничем и не отличаются о обычных директорий, как и ветки).
0
asolntsev #
Отличный мануал, спасибо!
0
qweewq #
Правильно писать: репОзиторий
0
VlK #
А, ну это да. В свое время мне встречались оба варианта.
НЛО прилетело и опубликовало эту надпись здесь
0
VlK #
о, точно :) Реверанс вашей внимательности!
0
wpm1 #
спасибо.
0
Spring_Storm #
Отличный пост! Всё разложено по полочкам и про ВСЁ написано.
0
VlK #
Ну… Когда-то было все, теперь же изрядная часть статьи устарела. По Интернету полно актуального материала — им и рекомендую пользоваться.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.