Использование Git

Немного рекламы

Введение

Я давно уже собирался попробовать какую-нибудь распределенную систему контроля версий, но глаза разбегались между Bazaar, Mercurial и Git. Однако, когда-то на хабре комрад VlK написал отличную статью, которая и подтолкнула меня попробовать в первую очередь именно Git. Когда читал ту статью, то возникали вопросы, ответы на которые находились простыми экспериментами или чтением документации. Именно поэтому я и решил написать эту статью, чтобы другим пользователям, которые решили использовать Git, было проще. Тем более, что сам я пользуюсь Windows, а это немного влияет на использование Git, о чем тоже будет написано.

А теперь это уже вторая версия статьи, так как с выходом Git 1.7 многое поменялось, в том числе и в лучшую сторону. :)

Распределенные системы контроля версий

Классические системы контроля версий, такие как CVS, SVN или, не к ночи будет упомянут, SourceSafe, разрабатывались таким образом, что всегда есть главный репозиторий, который содержит все изменения в коде, который через него прошел. В классических системах контроля версий понятие репозитория и рабочей копии исходников жестко разделены. В рабочей копии разработчиков находится слепок с текущего состояния исходных кодов в репозитории.

В противоположность этого, в распределенных системах контроля версий у каждого разработчика есть свой репозиторий, причем папка с репозиторием и есть папка с исходниками (вроде бы последнее утверждение справедливо не для всех распределенных систем, но для Git это так). Более того, в общем случае может не быть какого-то одного главного репозитория, а все они могут быть равнозначны. Другой вопрос, что в реальности наверняка есть один репозиторий, который считается главным, и куда попадают исходники для окончательной сборки. Но зато распределенная система позволяет строить иерархии репозиторий, когда разработчик отправляет изменения в один репозиторий, который для него считается главным, а затем изменения из нескольких таких главных репозиториев отправляют в самый главный репозиторий.

Кроме того распределенные системы удобны в том случае, если разработчик не имеет постоянного доступа к главному репозиторию. В этом случае алгоритм его работы будет следующий:

  1. Разработчик клонирует исходный репозиторий себе на компьютер
  2. Для каждого изменения разработчик заводит отдельную ветку в своем локальном репозитории
  3. После всех изменений он не сливает все ветки в главную ветку (ту, что в Git называется master), а делает commit в отдельные ветки, которые были созданы.
  4. При получении доступа к главному репозиторию он обновляет ветку master своего репозитория. Так как он эту ветку не трогал, то конфликтов здесь возникнуть не должно.
  5. После этого он начинает сливать изменения из каждой ветки в главную, исправляя полученные при этом конфликты (куда ж без них :( ).
  6. Теперь, когда изменения добавлены в обновленную версию главной ветви, можно отправлять изменения в главный репозиторий. Сделать он может это либо как в классических системах контроля версий отправляя изменения непосредственно в репозиторий, если разработчик имеет на это права, либо по почте в виде патча разработчику, который заведует главным репозиторием.

Изначально Git создавался Линусом Торвальдсом (ага, тем самым) с целью, создать такую систему контроля версий, в которой можно было бы легко создавать ответвления от основной ветки исходников, а также чтобы максимально облегчить обмен изменениями через привычные (особенно в линуксе) патчи. Ведь согласитесь, что удобно отправлять свои изменения по обычной электронной почте, не имея доступа на запись в репозиторий.

Начало работы

Будем считать, что Git вы уже установили. Для того чтобы разобраться как с ним работать, не будем пока использовать никакие графические оболочки. Но для демонстрации состояния репозитория и ветвей я буду делать скриншоты из программы QGit, которая очень наглядно все показывает.

Давайте начнем разбираться с Git на таком примере. Создадим в отдельной папке (у меня она будет называться demo) два файла. Один из них с именем hello.py (скрипт на Python, но в данном случае это не важно) будет иметь следующее содержимое:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

print "Hello, Git"

А второй файл пока будет пустым и называться описание.txt, чтобы посмотреть как Git работает с файлами, содержащими русские буквы в названии.

Таким образом, структура папок у нас на данный момент такая:

`---demo
    |---hello.py
    `---описание.txt

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

git init

После этого в консоли будет выведено сообщение о том, что пустой репозиторий создан.

Initialized empty Git repository in .../demo/.git/

Здесь вместо "..." у вас будет написан полный путь к вашей папке demo.

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

Теперь наше дерево папок выглядит так:

`---demo
    |---hello.py
    |---описание.txt
    `---.git
        |---config
        |---description
        |---HEAD
        |---hooks
        |   |---applypatch-msg.sample
        |   |---commit-msg.sample
        |   |---post-commit.sample
        |   |---post-receive.sample
        |   |---post-update.sample
        |   |---pre-applypatch.sample
        |   |---pre-commit.sample
        |   |---pre-rebase.sample
        |   |---prepare-commit-msg.sample
        |   `---update.sample
        |---info
        |   `---exclude
        |---objects
        |   |---info
        |   `---pack
        `---refs
            |---heads
            `---tags

Давайте пока не обращать внимания на содержимое папки git.

Репозиторий создан, но он, как уже говорилось, пока пустой, созданные нами файлы в него не добавлены. Чтобы увидеть в каком состоянии находится репозиторий, в той же папке demo выполним команду

git status

В результате увидим следующий текст:

# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       hello.py
#       "\356\357\350\361\340\355\350\345.txt"
nothing added to commit but untracked files present (use "git add" to track)

Здесь Git говорит нам, что мы находимся в данный момент в главной ветви репозитория (On branch master) в начальном коммите. Не знаю как можно перевести слово commit (фиксация?), чтобы был понятен контекст, поэтому давайте использовать в дальнейшем такой жаргон.

Также Git пишет, что два у нас есть два файла, которые не находятся в репозитории, имена их hello.py и некий \356\357\350\361\340\355\350\345.txt. Как можно догадаться, последнее имя - это файл описание.txt. По умолчанию для символов, не попадающих в первые 128 символов кодовой таблицы ASCII, Git выводит их коды. Давайте отключим эту особенность.

Настройки Git

Теперь немного отвлечемся на настройки Git для более удобной работы, это приходится делать в первую очередь, чтобы решить проблемы русскими буквами в путях, а также в Windows желательно настроить кодировку для текста комментариев коммитов. Но обо всем по порядку.

Все настройки Git хранит в текстовых файлах. Таких файлов несколько, формат их одинаковый, различия этих файлов состоит в области их действия.

Один такой файл настроек находится по адресу C:\Program Files\Git\etc\gitconfig или аналогичном, в зависимости от того, куда установлен Git.

Второй файл лежит в папке профиля пользователя, например, в Windows XP это файл C:\Documents and Settings\USERNAME\.gitconfig.

А третий файл настроек лежит в каждом репозитории в папке .git и носит имя config.

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

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

Все настройки поделены на разделы, а формат файлов config выглядит следующим образом:

[Раздел1]
    Параметр1 = значение
    Параметр2 = значение

[Раздел2]
    Параметр3 = значение
    Параметр4 = значение

Теперь вернемся к нашему репозиторию. Давайте отключим представление русских букв в виде их кодов. Для этого достаточно установить параметр quotepath из раздела core в значение false.

Откроем файл config в папке .git, которую мы создали, и добавим в раздел [core] следующую строку

quotepath = false

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

[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	symlinks = false
	ignorecase = true
	quotepath = false

Снова выполним команду

git status

Теперь то, какой вид примет сообщение будет зависеть от установленной версии Git. Дело в том, что под Windows Git версии до 1.7 русские буквы в консоль выводил кракозяблами, поэтому в старых версиях Git предыдущая команда выведет следующее сообщение:

# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       hello.py
#       юяшёрэшх.txt
nothing added to commit but untracked files present (use "git add" to track)

Интересующий нас файл стал в виде кракозяблов. На этот раз виновата убогая виндовая консоль, которая выводит текст еще в DOS-овской кодировке CP866. Под линуксом такой проблемы быть не должно.

Как полностью решить эту проблему я, к сожалению, не знаю. Успокаивает то, что графические программы (тот же QGit) показывает русские буквы правильно.

UPDATE: Как подсказал в комментариях DarkHobbit, кодировку, в которой Windows выводит текст можно изменить, выполнив команду

chcp 1251

Начиная с Git 1.7.0.2 такой проблемы больше нет и текст сообщения примет такой вид:

# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       hello.py
#       описание.txt
nothing added to commit but untracked files present (use "git add" to track)

Для Windows желательно установить еще один параметр, который отвечает за кодировки комментариев к коммитам. Дело в том, что Git по умолчанию для них использует кодировку UTF-8, что идеологически верно, но, опять же, виндовая консоль для ввода текста с клавиатуры использует кодировку Windows-1251. Поэтому добавим настройку, которая указывает, что именно эту кодировку и нужно использовать для комментариев. Для этого установим параметр commitencoding из раздела i18n в значение Windows-1251.

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

[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	symlinks = false
	ignorecase = true
	quotepath = false
[i18n]
	commitencoding = Windows-1251

Сейчас мы исправляли тексты конфигов вручную, однако в Git есть команда config, которая сама может вносить изменения в эти файлы, тогда нам не придется искать нужные файлы. В простейшем виде формат этой команды следующий:

git config <область действия> <раздел>.<параметр> <значение>

Здесь <область действия> задает то, в какой из выше перечисленных файлов настроек мы хотим внести изменения. Здесь возможны 4 варианта:

  • Ничего не указывать, тогда настройки будут изменены в репозитории, в папке которого был вызван Git.
  • --global, тогда настройки будут изменены в файле профиля пользователя
  • --system, тогда настройки будут изменены в файле папки etc для всех пользователей
  • --file, тогда нужно будет затем указать путь до конкретного файла, куда должны быть внесены изменения.

Последние два варианта мы трогать не будем, в наш созданный репозиторий мы уже внесли изменения, давайте установим те же настройки в общем конфиге пользователя.

Для этого выполним следующие две команды:

git config --global core.quotepath false

git config --global i18n.commitencoding Windows-1251

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

Также есть возможность смотреть значения интересующих нас параметров через саму команду config, для этого надо воспользоваться следующей записью:

git config <область действия> --get <раздел>.<параметр>

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

>git config --global --get i18n.commitencoding Windows-1251
Windows-1251

>git config --global --get core.quotepath
false

Чтобы не заставлять Git принимать из консоли тексты комментариев в кодировке Windows-1251, а использовать UTF-8, можно было бы установить параметр core.editor. Эта настройка хранит путь до внешнего редактора, в котором будут вводиться комментарии, тогда при коммите можно было бы использовать указанный редактор, который может работать с текстом в кодировке UTF-8, но так как на протяжении статьи мы будем все действия производить из консоли, то нам придется отказаться от использования Unicode для ввода комментариев.

Но после отказа от кодировки UTF-8 появится другая проблема, связанная с использованием графической оболочки, которая вызывается командой

git gui

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

Добавление файлов в репозиторий

Возвращаемся к нашим баранам. Напомню, что мы создали пустой репозиторий с помощью команды

git init

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

Для начала добавим файлы с указанием полного имени файла.

git add hello.py

Теперь выполним команду

git status

В результате увидим:

  1. On branch master
  2. Initial commit
  3. Changes to be committed:
  4. (use "git rm --cached <file>..." to unstage)
  5. new file: hello.py
  6. Untracked files:
  7. (use "git add <file>..." to include in what will be committed)
  8. описание.txt

Файл hello.py был добавлен в репозиторий и ожидает коммита, чтобы там окончательно обосноваться. описание.txt так и остался среди так называемых Untracked файлов, то есть среди файлов, за изменениями которых Git не следит.

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

git add .

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

# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   hello.py
#       new file:   описание.txt
#

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

Два файла добавлены и ожидают коммита. Давайте, наконец, зафиксируем изменения.

git commit -m "Первый коммит"

Здесь параметр -m говорит Git, что комментарий к коммиту мы будем вводить прямо в консоли, а не во внешнем редакторе. После выполнения этой команды мы увидим следующее сообщение

[master (root-commit) 2a436f3] Первый коммит
 2 files changed, 6 insertions(+), 0 deletions(-)
 create mode 100644 hello.py
 create mode 100644 описание.txt

Здесь еще раз приводится наша фраза-комментарий Первый коммит. Кроме того нам дана краткая сводка изменений, выполненных во время этого коммита. В программе QGit состояние репозитория будет отображено следующим образом:

А команда git status теперь нам выдаст следующее сообщение:

# On branch master
nothing to commit (working directory clean)

Игнорирование файлов

Команда git add . для добавления файлов очень удобна, но при разработке, как правило, образуются файлы, которые нет смысла хранить в репозитории, например, скомпилированные exe-шники, объектные файлы, или другие временные файлы, создаваемые компилятором. Разумеется, можно каждый раз аккуратно добавлять файлы, используя маску, чтобы исключать лишние файлы, но это ведь не удобно, хотелось бы, чтобы Git сам не обращал внимания на некоторые из файлов.

Пусть, например, мы хотим игнорировать все файлы *.pyc, которые создает интерпретатор Python, а также временные файлы *.tmp. Для этого заходим в папку репозитория и находим файл .git\info\exclude и открываем его в текстовом редакторе. В нем мы можем добавлять сколько угодно масок, по одной на каждой строке. Мы впишем в конец две строки:

  • .pyc
    *.tmp

Чтобы проверить, что исключения работают, создадим в нашей папке еще файл temp.tmp:

`---demo
    |---hello.py
    |---temp.tmp
    |---описание.txt
    `---.git
        |---...

Если теперь опять выполним команду git status, то Git не обратит внимания не то, что появился новый файл temp.tmp, и скажет, что изменений не произошло:

# On branch master
nothing to commit (working directory clean)

Если вы используете для разработки среду Visual Studio, то в файл exclude достаточно ввести следующие строки, чтобы Git не пытался следить за скомпилированными файлами.

bin
obj

Внесение изменений

Отлично! Теперь нужные файлы попали в репозиторий, а ненужные остались вне его. Пришло время приступить к изменениям в нашей программе.

Добавим описание в файл ''описание.txt. Пусть это будет простая строка:

Описание программы hello.py

Смотрим на статус репозитория с помощью команды git status:

# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   описание.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

Отсюда мы узнали, что изменился файл описание.txt, но изменения еще не были зафиксированы коммитом. Давайте зафиксируем. Как уже говорилось выше, нам предварительно нужно использовать команду git add. Но у нас есть выбор, либо выполнить две команды:

git add .

git commit -m "Добавлено описание"

Либо одну:

git commit -a -m "Добавлено описание"

А можно параметры -a и -m склеить в один:

git commit -am "Добавлено описание"

Параметр -a в последнем случае говорит о том, что нужно в коммит добавить все измененные файлы, включая файлы в подкаталогах, если бы они у нас были. Правда, здесь есть одно "но". Если команда git add . добавляет в коммит как измененные, так и новые файлы, то git commit -a не добавляет новые файлы, для них все-равно нужно выполнять команду git add.

Теперь история ветки репозитория выглядит следующим образом:

Работа с ветками

Удобная работа с ветками - конек Git. Давайте убедимся в том насколько легко создавать и сливать ветки в репозитории.

Создадим ветку "hello" для нашего репозитория. Для этого достаточно выполнить команду

git branch hello

После этой команды Git ничего в консоль не выводит, но если мы посмотрим дерево репозитория, то увидим, что кроме ветви "master" появилась ветвь "hello":

Переключимся на новую ветку с помощью следующей команды:

git checkout hello

Git на эту команду выведет скромное сообщение

Switched to branch 'hello'

В QGit мы тоже увидим, что активной ветвью стала "hello":

Вместо перечисленных двух команд мы могли бы использовать команду

git checkout -b hello

Здесь параметр -b говорит, что ветвь (branch) нужно создать.

Для эксперимента создадим еще одну ветвь:

git checkout -b test

Теперь у нас три ветви, и активной стала "test".

Останемся на этой ветви и сделаем какие-нибудь изменения в файле hello.py, например, добавим еще одну строку.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

print "Hello, Git"
print "Hello, branch"

Сделаем коммит изменений:

git commit -a -m "Изменения в hello.py"

В репозитории ветка "test" вырвалась вперед:

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

Мы можем вернуться на основную ветку с помощью команды

git checkout master

и убедиться, что файл hello.py остался таким, каким он был до изменений в ветке "test", а при переключении обратно на ветку "test" мы опять получим измененный файл. Веток может быть много, и мы всегда будем иметь нужную версию исходников. Особенно это удобно для экспериментов над программой, когда нужно внести какое-то изменение, которое потом не должно попасть в основную ветку. Мы всегда можем легко наделать нужное количество ответвлений, а затем просто про них забыть, но лучше удалить.

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

git checkout hello

Добавим вторую строчку описания.

Описание программы hello.py
А здесь более подробное описание

И снова сделаем коммит:

git commit -a -m "Исправления в описании"

Теперь у нас есть две ветки кроме главной, где внесены изменения. Настало время слить эти изменения в главную ветвь "master".

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

git checkout master

А теперь выполняем команду

git merge test

Теперь файл hello.py в главной ветке тоже получил вторую строку. Очень наглядно состояние репозитория показывает QGit:

А теперь возьмем изменения и из ветви "hello":

git merge hello

Теперь файл описание.txt главной ветки тоже дополнился новой строкой. А график репозитория выглядит теперь следующим образом:

Обратите внимание, что в ветке "master" теперь файлы включают изменения из "hello" и "test", но сами ветки "hello" и "test" остались в том виде, в каком они были до слияния. Мы можем, например, в ветку "test" влить изменения из ветки "master", выполнив следующие команды:

git checkout test

git merge master

Теперь в обоих этих ветках содержатся одинаковые исходники, но в ветке "hello" остается старый файл hello.py, но нас это не должно волновать, так как больше нам эта ветка не понадобится. Давайте ее удалим, для чего выполним следующую команду:

git branch -d hello

Здесь параметр -d говорит, что ветку "hello" нужно удалить. При этом обязательным условием является то, что изменения в указанной ветке обязательно должны быть предварительно слиты с другой веткой. Если это не так, а удалить ветку все-таки надо, то вместо параметра -d нужно использовать параметр -D.

Теперь упоминания о ветке "hello" в репозитории нет:

Клонирование

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

git clone <путь>

Здесь <путь> - это путь к нашему первому репозиторию (возможно, путь придется взять в кавычки, если в нем есть пробелы). После выполнения этой команды будет создан каталог, в котором будут лежать исходники и папка .git. До этого в нашем репозитории уже были слияния веток, так и в клоне они останутся. Вот как показывает структуру веток клона QGit:

В Git, в отличие от SVN, есть одна, возможно неприятная, особенность. Дело в том, что при клонировании файл exclude будет создан пустым, и его придется заполнять заново. Также не будут клонированы локальные для репозитория настройки из файла config. Об этом надо помнить, если вы используете какие-то локальные настройки, ну а про файлы-исключения приходится помнить всегда.

Теперь посмотрим как происходит обмен изменениями между двумя репозиториями. Для начала пусть изменения произошли в главном репозитории. Переключимся на ветвь master с помощью команды

git checkout master

Затем изменим содержимое файла hello.py, например, на следующее:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

for i in range (3):
	print "Hello, Git"

После этого сделаем коммит.

git commit -a -m "Изменения в главном репозитории"

Теперь структура репозитория выглядит так:

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

git pull

Git немного подумает, обновит файлы и выдаст следующую информацию:

From ...\samples\demo
   e0d3eb8..42ede2b  master     -> origin/master
Updating e0d3eb8..42ede2b
Fast forward
 hello.py |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

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

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

Теперь рассмотрим ситуацию, когда клон отправляет изменения в родительский репозиторий. Изменим в клоне текст файла hello.py на следующий:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

for i in range (3):
	print "Hello, Clone"

И закоммитим это изменения в ветви master.

git commit -a -m "Изменения в клоне"

Вот как выглядит теперь структура клона:

Чтобы отправить изменения родителю достаточно выполнить следующую команду:

git push

До версии Git 1.7 эта команда была бы выполнена, но с длинным предупреждением о том, что вы собираетесь отправить изменения в удаленный репозиторий, но в данный момент с ним может кто-то работать, и это может быть причиной проблем. Начиная с версии 1.7 разработчики поступили просто: по умолчанию нельзя выполнять операцию push в удаленный репозиторий для веток (в нашем случае для ветки master), если в этом самом удаленном репозитории эта самая ветка является текущей (то есть над ней кто-то может работать). Поэтому начиная с версии Git 1.7 мы получим следующую ошибку:

Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 403 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To H:\Черновики\git\samples 2\demo
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to 'H:\Черновики\git\samples 2\demo'

В принципе, тут уже написано что можно сделать. А именно, существует параметр receive.denyCurrentBranch, который определяет поведение Git в таких случаях. По умолчанию мы не можем выполнять такую небезопасную операцию push, но мы можем установить этот параметр в значение warn, тогда Git поворчит предупреждениями, но команду push выполнит. Такое поведение было по умолчанию в Git до версии 1.7. А если мы установим значение параметра receive.denyCurrentBranch в значение ignore, то операция push будет выполнена безо всяких предупреждений.

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

git config receive.denyCurrentBranch ignore

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

git push

На этот раз все проходит удачно:

Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 403 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To H:\Черновики\git\samples 2>><<

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

Если теперь в главном репозитории выполнить команду

git status

то мы увидим:

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   hello.py
#

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

Однако структура родительского репозитория у нас верная:

Чтобы синхронизировать исходники с репозиторием нужно выполнить команду

git reset --hard

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

Репозитории без рабочей копии исходников

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

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

git init --bare

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

`---demo
    |---config
    |---description
    |---HEAD
    |---hooks
    |   |---applypatch-msg.sample
    |   |---commit-msg.sample
    |   |---post-commit.sample
    |   |---post-receive.sample
    |   |---post-update.sample
    |   |---pre-applypatch.sample
    |   |---pre-commit.sample
    |   |---pre-rebase.sample
    |   |---prepare-commit-msg.sample
    |   `---update.sample
    |---info
    |   `---exclude
    |---objects
    |   |---info
    |   `---pack
    `---refs
        |---heads
        `---tags

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

git clone <путь до репозитория>

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

Initialized empty Git repository in H:/Черновики/git/samples 2/clone/demo/.git/
warning: You appear to have cloned an empty repository.

Но нас это пока не должно смущать. Теперь создадим в клоне пустой файл readme.txt. Добавим его в коммит:

git add .

git commit -am "Первый коммит"
[master (root-commit) 01319d8] Первый коммит
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 readme.txt

А вот теперь, при первой отправке изменений в репозиторий мы не можем просто выполнить команду git push, дело в том, что у нас в данный момент нет связи ветки master клона и какой-либо ветки в исходном репозитории. Точнее говоря, в главном репозитории у нас сейчас вообще нет никаких веток, в том числе и master.

Но это нас не должно пугать, потому что мы можем создать ветку master в главном репозитории прямо во время операции push. Для этого достаточно один раз выполнить команду

git push origin master

Здесь слово origin указывает на то, что изменения должны быть отправлены в "оригинальный" репозиторий, с которого был сделан клон, а master обозначает название ветки, куда должны быть отправлены изменения. Если этой ветки до сих пор не было, то она будет создана. После этой операции Git напишет следующее сообщение:

Counting objects: 3, done.
Writing objects: 100% (3/3), 244 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To H:\Черновики\git\samples 2\demo
 * [new branch]      master -> master

После этой операции работа с главным репозиторием ничем не будет отличаться от того, что было описано ранее с той лишь разницей, что операция push не будет вызывать проблем и не надо будет вызывать в репозитории команду git reset --hard, так как там рабочей копии исходников нет (поэтом у такая операция и не будет выполнена).

Даже если теперь вы заходите сделать еще один клон репозитория, то уже не надо будет использовать команду git push origin master, достаточно будет команды git push.

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

Другие статьи про Git вы можете найти в разделе Инструменты.

Немного рекламы

Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
5 stars

Рейтинг 4.7/5. Всего 107 голос(а, ов)



Владимир 11.09.2009 - 10:12

Спасибо за статью! В ней всё понятно описано, в отличии от других

Jenyay 11.09.2009 - 10:30

Спасибо, рад, что статья понравилась :)

k0sh 15.09.2009 - 12:02

Отличная статья, жду продолжения. Единственное, что не совсем понял: после команды git reset --hard изменения в главном репозитории сбросятся к последнему коммиту, то есть к состоянию после git push, но что делать если эти изменения нам важны?

Jenyay 15.09.2009 - 12:23

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

В принципе, есть еще команда git reset --merge, которая, похоже, и делает то, что нужно для этого случая, то есть склеивает изменения. Правда, я ей не пользовался, надо посмотреть как она работает.

Дмитрий 01.11.2009 - 17:36

Спасибо за статью, все понятно.
Но у меня возникла одна проблема. Я изменил редактор для комментариев изменив параметр core.editor на редактор notepad++ и проблема с кодировкой комментов была решена, но подпись(имя автора) к коментариям осталась по всей видимости в кодировке win frowning smiley. Есть идеи как это можно исправить?

Jenyay 01.11.2009 - 18:20

Не пробовал писать имя русскими буквами, но попробую, потому что все-равно в будущем собирался описывать этот параметр в будущей статье.

Возможно, будет достаточно перевести файл конфига в кодировку utf-8.

Антон 13.01.2010 - 19:48

Действительно большое спасибо всё ясно изложено и понятно для начала использования Git

DarkHobbit 22.01.2010 - 09:20

О кодировках...

За статью в целом спасибо. Однако неприятно удивило описание проблем с кодировками. Это git до сих пор так убог в части интернационализации по сравнению с svn, или это всё же автор чего-то не осилил?
Для сравнения: у меня работает сервер svn в гетерогенной сети. Клиенты разные - есть виндовые, есть линуксовые, причём со старым линуксом, где локаль ещё была KOI-8. Из винды, в свою очередь, коммиты часть программистов делает из консоли, часть из TortoiseSVN. НИКАКИХ проблем с кодировками (в т.ч. при чтении закоммиченного из другой ОС) ни разу не видел и не производил никаких танцев с бубном.

И кстати, "убогая виндовая консоль" отлично перенастраивается с cp866 на другие кодировки - см. команду chcp. Но при работе с svn мне не нужно было даже этого.

Jenyay 22.01.2010 - 09:52

DarkHobbit, Спасибо. Посмотрел chcp, действительно консоль можно заставить с помощью этой команды выводить текст в кодировке 1251.

Добавил в статью упоминание про эту команду. Жаль, что ее надо вводить при каждом открытии консоли. Или может есть способ заставить ее по умолчанию выводить текст в нужной кодировке?

Антон 28.05.2010 - 11:10

Из всех статей найденных мною в Интернет эта наиболее вразумительная и понятная для людей которые раньше дела с системами контроля версий не имели(я в том числе).
Хотелось бы увидеть описание того как можно изменить редактор для комментариев на notepad++.
Спасибо!

Jenyay 28.05.2010 - 19:01

А редактор подключить просто:

git config --global core.editor "Путь до редактора"

k0rg 04.08.2010 - 15:23

Исключения

Можно просто создать файл .gitignore в папке с проектом и этот файл тоже попадёт под контроль версий и будет клонироваться. По поводу кодировок: есть параметры i18n.commitencoding и i18n.logoutputencoding. У меня редактор для каментов внешний (vim) и значения параметров такие:
i18n.commitencoding=cp1251
i18n.logoutputencoding=cp866
git commit, когда повторяет после коммита комент - пишет кракозябрами:
$ git commit
[master bad5269] ═ряюыэхэшх ЁхяючшЄюЁш 
Но git log уже нормально:
$ git log
<skipped>

    Наполнение репозитория

Jenyay 29.08.2010 - 12:18

k0rg, попробовал класть .gitignore в корень проекта - не помогает.

Julia 13.10.2010 - 19:06

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

gapsf 29.10.2010 - 14:13

У меня после
git config --global core.quotepath false
кириллица в имени фала не отображается вообще, типа " .txt":

  1. Not currently on any branch.
  2. Untracked files:
  3. (use "git add <file>..." to include in what will be committed)
  4. .txt

nothing added to commit but untracked files present (use "git add" to track)

А после команды
git config --global i18n.commitencoding Windows-1251
ничего не меняется:
commit 33c80438543a9152520de233979e7a9f578e85ce
Author: unknown <(none)>
Date: Fri Oct 29 17:06:59 2010 +0700

    <D0><F3><F1><F1><EA><E8><E9>

И еще - как быть с верхним/нижним регистром? В винде то по барабану, а гит Русский.txt и руский.txt считает разными файлами.
Чего посоветуете?

Jenyay 29.10.2010 - 20:35

Знаете, такое ощущение, что, если нужно хранить файлы с русскими буквами, то удобнее использовать Bazaar, а не Git. Там с ними проблем намного меньше.

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

А по поводу заглавных букв, имхо, как раз правильно эти файлы считать разными, и Bazaar тоже будет считать разными.

N.A.Y. 18.11.2010 - 23:57

Спасибо, очень помогло, а главное - написано понятно. Буду ждать продолжения. Если хочется использовать windows, то зачем тогда заморачиваться с Русский.txt и русский.txt? А если стоит linux,то и проблем не будет.

solo 02.12.2010 - 17:12

to Jenyay: а почему это правильно считать Русский.txt и русский.txt разными? поясните

Jenyay 02.12.2010 - 21:44

to solo: это скорее программерская привычка считать, что переменные Foo и foo - разные переменные. Какой-то объективной причины и не назову, точнее назову доводы "за" с обеих сторон. Тут скорее кому как удобнее, мне как-то больше нравится, когда такие файлы считаются разными.

 15.08.2011 - 15:22

to solo: Потому, что Русский.txt и русский.txt в UNIX-подобных ОС - всегда разные файлы (а в Windows одинаковые). Отсюда и с переменными такая же аналогия.

sasha 05.09.2011 - 15:54

подскажите,как создать репозиторий папки в другом месте?
например,папка находится user/project/my_project,а мне надо создать репозиторий в папке user/Dropbox/projects/work,и соответственно добавлять/обновлять в дропбоксе?

Jenyay 05.09.2011 - 16:22

В этом случае я бы сделал в папке user/Dropbox/projects/work основной репозиторий без дерева исходников, а в user/project/my_project - репозиторий, где происходила бы вся работа. И после коммитов в my_project отправлял бы изменения с помощью git push в репозиторий, который лежит в папке дропбокса.

sasha 05.09.2011 - 16:42

а,как сделать основной репозиторий,без дерева исходников в папке user/Dropbox/projects/work, и как после коммита проекта, отправить изменения git push ?
просто,я натолкнулся на вас,здесь все разжевано,и имею наглостьgrinning smiley что бы мне и это разжевали

Jenyay 05.09.2011 - 16:49

Вечером домой доберусь, напишу поподробнее. :)

sasha 05.09.2011 - 16:53

буду ждать

Jenyay 05.09.2011 - 21:16

Чтобы создать репозиторий без исходников надо выполнить команду:

git init --bare user/Dropbox/projects/work/my_project

После этого клонируем этот репозиторий:

git clone user/Dropbox/projects/work user/project/my_project

В user/project/my_project создаем все нужные файлы, после этого добавляем их в репозиторий:

git add .
git commit -m "Первый коммит"

После этого отправляем изменения в репозиторий user/Dropbox/projects/work/my_project:

git push origin

После этого что-то исправляем в проекте, добавляем изменения с помощью:

git add .
git commit -m "Комментарий для коммита"

После этого опять отправляем изменения в синхронизированный с помощью DropBox репозиторий:

git push

А вообще может быть стоит задуматься, чтобы вместо DropBox'а использовать какой-нибудь удаленный репозиторий?

sasha 05.09.2011 - 21:19

спс!
я добавил тебя в свой круг (гугл+)

Jenyay 05.09.2011 - 21:28

Ага, тоже тебя добавил :)

Андрей 09.09.2011 - 23:38

А у меня что-то не выходит с русскими буквами в путях - вместо кирилицы выводятся 0 (квадратного вида) и смена символьной таблицы на 1251 не помогла.

А еще вопрос в догонку. Как заставить работать внешний редактор? Добавил через консоль пусть до notepad++ посмотрел в файле .gitconfig появилась строка:

editor = c:/Program Files (x86)/Notepad++/notepad++

но работать не хочет выводится ошибки.

Jenyay 10.09.2011 - 11:04

По поводу редактора. Подправьте строку с настройкой редактора на

editor = \"c:/Program Files (x86)/Notepad++/notepad++\"
а еще лучше для надежности на
editor = \"c:/Program Files (x86)/Notepad++/notepad++.exe\"

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

Андрей 10.09.2011 - 16:42

Странная какая-то ситуация. Поменял настройки а ошибка вылетает со старым адресом (не рабочим). Пробовал переназначать на других уровнях, пробовал ставить через консоль, но чего не помогает. Крайне странно. Посмотрел в процессах вроде не висит нечего связанного с GIT.

А насчет кирилицы. Я поставил последнюю версию не помогает. ну в принципе это по боку т.к. интересно было чисто с теоретической стороны.

Jenyay 10.09.2011 - 18:03

Может для конкретного репозитория настройки установлены неправильные?

Андрей 10.09.2011 - 18:17

Нет я изначально прописывал на глобальном уровне (через консоль), потом когда понял что настройки не правильные стал их сбрасывать на другие варианты но при ошибке выдает старый путь (не правильный). Потом пробовал прописать для репозитория и пробовал сбросить глобальные, но не помогает. Я вроде не когда не слышал что GIT эти конфиги где-то кеширует, да и через консоль по идее должно бы было все сбросится. Сейчас попробую комп перезапустить. Если не поможет переустановлю GIT.

Jenyay 10.09.2011 - 18:49

Просто приоритет у локальных настроек больше, чем у глобальных, поэтому первая мысль - проверить, что прописано в локальных настройках.

Андрей 10.09.2011 - 18:56

Ну я собственно также и подумал. Перезапустил комп не помогло, переустановили Git - не помогло, после переустановки новый репозиторий создал - тоже не помогло. Я просто в шоке. Удалял через сторонюю програму которая обычно все хвосты подчищает, но не в случае с GIT он видать все таки еще где-то следы оставил.Сейчас попробую скачать предыдущюю версию (1.7.4)

Андрей 10.09.2011 - 19:28

Заставил работать. Глупая ситуация вышла - оказывается у меня была прописана переменная GIT_EDITOR с неправильным путем. Вот она и подхватывалась.

Andrew 17.12.2011 - 17:42

Спасибо за статью!

Появилась потребность использовать git. Достаточно подробно все написано, но кое-что не понятно.
Подскажите как организовать синхронизацию сайта на удаленном хостинге с локальной версией на windows? Цель - делать поиск и редактирование файлов на локальной машине, потом сливать все на хостинг. Можно ли настроить мастер-ветку на хостинге?

Jenyay 17.12.2011 - 18:21

Вообще сервер с git - это отдельная тема, в состав git входит свой сервер, правда, не знаю, сделали его версию под Windows. И надо обратить внимание, с какими протоколами может работать git.

MirrorBoy 01.02.2012 - 13:39

Благодарности и пожелания

Замечательная статья. Спасибо огромное
Все описанное проделывал на Windows Server 2008 и TurtoiseGit
Скриншотики немного отличаются, но в основном все сходится.
А вот что осталось совсем непонятным, это нарисованные разным цветом линии в колонке Graph. Почему на некоторых узлы круглые, а на некоторых квадратные.
И почему на определенном этапе у нас было три бранча: master, test и hello, а линий всего две, красная и черная?

И еще один вопрос: могу ли я не делая Pull, увидеть были ли в центральном репозитории какие-нибудь изменения?

Заранее спасибо за ответы

MirrorBoy 01.02.2012 - 15:06

Еще вопросик

Как вытащить из глобального репозитория отдельную поддиректорию и работать только с ней?

Jenyay 01.02.2012 - 19:47

По поводу GUI особо ничего не скажу - сам я с git работал через консоль, но мне казалось, что кружочки - это обычные коммиты, а квадратики - те, от которых есть ответвления.

> И почему на определенном этапе у нас было три бранча: master, test и hello, а линий всего две, красная и черная?

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

> могу ли я не делая Pull, увидеть были ли в центральном репозитории какие-нибудь изменения?

Посмотрите вот эту ссылку - http://stackoverflow.com/questions/2514270/how-to-check-for-changes-on-remote-origin-git-repository

> Как вытащить из глобального репозитория отдельную поддиректорию и работать только с ней?

Так сходу не нашел в документации.

Andrew 03.03.2012 - 22:23

Имя репозитория

Подскажите, где узнать имя моего удаленного репозитория?

bva 29.06.2012 - 12:30

Великолепно!
Большое спасибо.

UbiVak 12.11.2012 - 10:27

Огромное спасибо!

GabrielleRamires 12.11.2013 - 10:45

Каталог.

Как добавить к существующему репозиторию (локальному) каталог, который находится двумя уровнями выше в иерархии каталогов?
Пробовал "git add ../../TxtFile/."
Выдает ошибку:
fatal: '../../TxtFile/.' is outside repository

Jenyay 12.11.2013 - 13:02

Этот каталог не должен быть выше по дереву, чем корень репозитория (где находится папка .git).

Dionis 01.12.2013 - 21:44

Самое нормальное введение в GIT

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

Dionis 01.12.2013 - 21:49

Самое нормальное введение в GIT

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

Сергей 14.02.2014 - 23:39

Огромное спасибо

Присоединяюсь, это самое лучшее введение в git из тех, что я встречал.

LIME 23.10.2014 - 23:54

+1

спасибо


Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 455