Использование Git
- Введение
- Распределенные системы контроля версий
- Начало работы
- Настройки Git
- Добавление файлов в репозиторий
- Игнорирование файлов
- Внесение изменений
- Работа с ветками
- Клонирование
- Комментарии
Введение
Я давно уже собирался попробовать какую-нибудь распределенную систему контроля версий, но глаза разбегались между Bazaar, Mercurial и Git. Однако, когда-то на хабре комрад VlK написал отличную статью, которая и подтолкнула меня попробовать в первую очередь именно Git. Когда читал ту статью, то возникали вопросы, ответы на которые находились простыми экспериментами или чтением документации. Именно поэтому я и решил написать эту статью, чтобы другим пользователям, которые решили использовать Git, было проще. Тем более, что сам я пользуюсь Windows, а это немного влияет на использование Git, о чем тоже будет написано.
А теперь это уже вторая версия статьи, так как с выходом Git 1.7 многое поменялось, в том числе и в лучшую сторону. :)
Распределенные системы контроля версий
Классические системы контроля версий, такие как CVS, SVN или, не к ночи будет упомянут, SourceSafe, разрабатывались таким образом, что всегда есть главный репозиторий, который содержит все изменения в коде, который через него прошел. В классических системах контроля версий понятие репозитория и рабочей копии исходников жестко разделены. В рабочей копии разработчиков находится слепок с текущего состояния исходных кодов в репозитории.
В противоположность этого, в распределенных системах контроля версий у каждого разработчика есть свой репозиторий, причем папка с репозиторием и есть папка с исходниками (вроде бы последнее утверждение справедливо не для всех распределенных систем, но для Git это так). Более того, в общем случае может не быть какого-то одного главного репозитория, а все они могут быть равнозначны. Другой вопрос, что в реальности наверняка есть один репозиторий, который считается главным, и куда попадают исходники для окончательной сборки. Но зато распределенная система позволяет строить иерархии репозиторий, когда разработчик отправляет изменения в один репозиторий, который для него считается главным, а затем изменения из нескольких таких главных репозиториев отправляют в самый главный репозиторий.
Кроме того распределенные системы удобны в том случае, если разработчик не имеет постоянного доступа к главному репозиторию. В этом случае алгоритм его работы будет следующий:
- Разработчик клонирует исходный репозиторий себе на компьютер
- Для каждого изменения разработчик заводит отдельную ветку в своем локальном репозитории
- После всех изменений он не сливает все ветки в главную ветку (ту, что в Git называется master), а делает commit в отдельные ветки, которые были созданы.
- При получении доступа к главному репозиторию он обновляет ветку master своего репозитория. Так как он эту ветку не трогал, то конфликтов здесь возникнуть не должно.
- После этого он начинает сливать изменения из каждой ветки в главную, исправляя полученные при этом конфликты (куда ж без них :( ).
- Теперь, когда изменения добавлены в обновленную версию главной ветви, можно отправлять изменения в главный репозиторий. Сделать он может это либо как в классических системах контроля версий отправляя изменения непосредственно в репозиторий, если разработчик имеет на это права, либо по почте в виде патча разработчику, который заведует главным репозиторием.
Изначально 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
В результате увидим:
- On branch master
- Initial commit
- Changes to be committed:
- (use "git rm --cached <file>..." to unstage)
- new file: hello.py
- Untracked files:
- (use "git add <file>..." to include in what will be committed)
- описание.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.