Работа с репозиторием Git

Настройка конфигурации

Существует три конфигурации git:

  • system - общесистемная конфигурация, распространяющаяся на всех пользователей системы,
  • global - конфигурация пользователя, действующая на все репозитории, с которыми работает этот пользователь,
  • local - локальная конфигурация репозитория, распространяется только на репозиторий.

Для редактирования конфигурации можно воспользоваться командой с указанием соответствующей опции:

$ git config --global --edit

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

[user]
    user = Vladimir Stupin
    email = vladimir@stupin.su

Доступ к репозиторию по протоколу HTTP (или HTTPS) должен осуществляться через веб-прокси, то его настройки можно прописать в файле конфигурации следующим образом:

[http]
    proxy = http://user:password@proxy.domain.tld:port

Где:

  • user - имя пользователя прокси-сервера. Если прокси-сервер не требует аутентификации, то имя пользователя и его пароль вместе со знаками двоеточия и собачки нужно опустить. Если пользователь и пароль не указаны, но всё-таки требуются, то они будут запрошены при обращении к веб-ресурсам.
  • password - пароль этого пользователя. Если пароль нежелательно сохранять в файле конфигурации, то его можно опустить вместе с двоеточием.
  • proxy.domain.tld - доменное имя (или IP-адрес) прокси-сервера.
  • port - номер порта прокси-сервера. По умолчанию используется порт 80 (?), при необходимости его можно опустить вместе с двоеточием.

Если для доступа к удалённому репозиторию по протоколу HTTP (или HTTPS) требуется аутентификация, то при каждом обращении к этому репозиторию будет запрашиваться логин и пароль. Чтобы не вводить логин и пароль при каждом обращении к репозиторию, можно настроить кэш или постоянное хранение учётных данных. Сделать это можно при помощи конфигурации следующего вида:

[credential]
    helper = cache --timeout 900
    helper = store --file ~/.git-credentials

При использовании сохранения учётных данных в кэше можно указать время в секундах, по умоланию используется время 900 секунд (15 минут). При использовании сохранения учётных данных в постоянном файл необходимо указать имя этого файла. Можно использовать хранение учётных данных как в кэше, так и в файле. В таком случае все источники будут перебираться по порядку до тех пор, пока в одном из хранилищ не найдутся нужные учётные данные.

Для аутентификации на удалённом репозитории по протоколу SSH необходимо загрузить на него свой публичный ключ и указать SSH-клиенту использовать приватный ключ для подключения к этому узлу сети.

Сгенерируем пару ключей:

$ ssh-keygen

Файл ~/.ssh/id_rsa.pub является публичным ключом, который необходимо установить на сервер. Файл ~/.ssh/id_rsa является приватным ключом, при помощи которого будет происходить аутентификация на сервере.

Для того, чтобы приватный ключ автоматически использовался при попытке подключения к серверу, можно создать файл ~/.ssh/config со следующим содержимым:

Host server.domain.tld
    User user
    AddKeysToAgent yes
    IdentityFile ~/.ssh/id_rsa

Изменение автора

Иногда бывает так, что репозиторий уже создан или обновлён, а в конфигурации репозитория ещё не была прописана правильная информация об авторе. В таком случае можно обратиться к предыдущему разделу и поправить конфигурацию репозитория, после чего остаётся только исправить автора в недавно добавленных фиксациях. Воспользуемся для этого перебазированием. С помощью команды git log находим идентификатор фиксации, после которой нужно внести исправления и запускаем команду:

$ git rebase -r 73431438c62c78831a1ad6ef368ad0431f6400ff \
--exec 'git commit --amend --no-edit --reset-author'

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

$ git rebase -r 73431438c62c78831a1ad6ef368ad0431f6400ff \
--exec 'git commit --amend --no-edit --author="Vladimir Stupin <vladimir@stupin.su>"'

Если нужно изменить данные всех фиксаций, включая начальную, то вместо указания идентификатора фиксации нужно указать опцию --root. В таком случае две приведённых выше команды примут следующий вид:

$ git rebase -r --root \
--exec 'git commit --amend --no-edit --reset-author'

$ git rebase -r --root \
--exec 'git commit --amend --no-edit --author="Vladimir Stupin <vladimir@stupin.su>"'

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

$ git filter-branch -f --env-filter '
GIT_AUTHOR_NAME="Vladimir Stupin"
GIT_AUTHOR_EMAIL="vladimir@stupin.su"
GIT_COMMITTER_NAME="Vladimir Stupin"
GIT_COMMITTER_EMAIL="vladimir@stupin.su"
'

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

$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_NAME" = "Vladimir Stupin" ];
then
    GIT_AUTHOR_EMAIL="vladimir@stupin.su";
    git commit-tree "$@";
else
    git commit-tree "$@";
fi'

Добавление новой начальной правки в репозиторий Git

Случилось так, что я решил поместить в репозиторий все свои программы, которые когда-то разрабатывал. С одной из программ приключилась такая история: я поместил в репозиторий найденную программу версии 1.1, внёс большое количество правок, выпустил версию 1.2, а потом нашёл на своём компьютере версию 1.0. Захотелось добавить эту версию программы перед начальной правкой в репоизтории. Мне удалось успешно провернуть этот трюк, после чего я нашёл ещё две предшествующие версии программы - 0.8 и 0.1. На сей раз я не просто провернул трюк, но и решил задокументировать его.

Создаём новый пустой репозиторий:

$ git init

Добавляем файлы в индекс:

$ git add *.cpp *.h

Добавляем комментарий к правке, заблаговременно выставив отметки времени правки:

$ env GIT_AUTHOR_DATE="Thu Mar 13 13:00:00 2003 +0500" GIT_COMMITTER_DATE="Thu Mar 13 13:00:00 2003 +0500" git commit -m "Версия 0.1. Тест OpenGL"

Заглядываем в журнал правок:

$ git log

И видим идентификатор правки, автора, отметку времени и комментарий к правке:

commit d2d5eb4017b88413ec9c1d5f47252d113bc07996
Author: Vladimir Stupin <vladimir@stupin.su>
Date:   Fri Mar 21 13:00:00 2003 +0500

    Версия 0.8

Заглядываем в каталог .git/objects и запоминаем имена всех каталогов, имя которых состоит из двух шестандцатеричных цифр.

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

$ cp -R new-root/.git/objects/{4a,84,95,a7,b9,c6,d0,d2} project/.git/objects/

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

$ git rebase --onto d2d5eb4017b88413ec9c1d5f47252d113bc07996 --root master

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

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

$ git add idpo.cpp main.cpp vmdl.cpp vmdl.h

И продолжаем перебазирование:

$ git rebase --continue

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

Использованные материалы:

Создание и публикация новой ветки

Переходим к правке, от которой будем растить новую ветку:

$ git checkout 01f6ee2c99a4fb15fab3177decc69147b4288c5e

Создаём новую ветку с именем v1.0b:

$ git branch v1.0b

Изменяем файлы, добавляем их в индекс:

$ git add idpo.cpp 

Подтверждаем изменения:

$ git commit -m "Версия 1.0b. Просмотр третьей версии IDPO-моделей Quake"

Заливаем ветку в вышестоящий репозиторий:

$ git push --set-upstream origin v1.0b

Возвращаемся к основной ветке:

$ git checkout master

Создание аннотированной метки

Добавление новой аннотированной метки:

$ git tag -m "Версия 1.0. Просмотр шестой версии IDPO-моделей Quake" v1.0 01f6ee2c99a4fb15fab3177decc69147b4288c5e

Просмотр всех меток в локальном репозитории:

$ git tag

Отправляем все метки в вышестоящий репозиторий:

$ git push --tags

Создание и слияние веток

Создаём новую ветку:

$ git branch idpo-v3

Смотрим на список имеющихся веток и видим, что сейчас мы по-прежнему находимся в ветке master:

$ git branch
  idpo-v3
* master

Переключаемся на ветку idpo-v3:

$ git checkout idpo-v3

Смотрим на список веток снова и видим, что теперь мы находимся в ветке idpo-v3:

$ git branch
* idpo-v3
  master

Меняем файлы, добавляем их в индекс:

$ git add idpo.cpp

Подтверждаем правку:

$ git commit -m "Версия 1.0b. Просмотр третьей версии IDPO-моделей Quake"

Возвращаемся обратно на главную ветку:

$ git checkout master

Просим выполнить слияние веток без внесения наращивания истории главной ветки правками из ветки idpo-v3 и без подтверждения слияния:

$ git merge --no-ff --no-commit idpo-v3

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

Добавляем изменённые файлы в индекс:

$ git add idpo.cpp main.cpp

Подтверждаем правку, объединяющую ветку idpo-v3 с веткой master:

$ git commit -m "Версия 1.1. Добавлена поддержка просмотра третьей версии IDPO-моделей Quake"

Просмотр истории ветвлений и слияний

Посмотреть на историю взаимоотношений веток можно при помощи команды:

$ git log --all --decorate --oneline --graph

Опция --all заставляет показывать правки во всех ветках, --oneline - выводить по одной правки в одной строчке, --graph - отображать взаимоотношения веток, --decorate - показывать названия, источники и метки веток.

Программы для управления репозиториями

Опробованы:

  • gitweb
  • cgit
  • gitblit
  • gogs
  • gitea

Не опробован:

  • gitolite3

Стоит попробовать настроить gitblit для работы под управлением сервера приложений uwsgi-plugin-jvm-openjdk-8 или uwsgi-plugin-servlet-openjdk-8

Поместить программы в репозитории

Создание нового пустого репозитория с одним лишь файлом REAMDE.md:

$ vim README.md
$ git init
$ git add README.md
$ git commit -m "first commit"
$ git remote add origin https://stupin.su/git/stupin/pup.git
$ git push -u origin master

Добавить репозитории:

  • [x] texture
  • [x] pup
  • [x] postadmin
  • [x] postadmin2
  • [x] flask-account
  • [x] parled12
  • [x] usr
  • [ ] wadview - на Pascal
  • [ ] vview - на Pascal
  • [ ] vga - на C и Assembler
  • [ ] quake/vmdl, оно же - view3d
  • [ ] quake/bsp, оно же - quake/wad
  • [ ] redfaction/vbm
  • [ ] daikatana/wal
  • [ ] calendar
  • [ ] gta-vc - перекодирование из adf в mp3 и обратно
  • [ ] riffscanner, bmscanner, musscanner, pwadscanner
  • [ ] umxscanner
  • [ ] scandir - рекурсивный список файлов
  • [ ] stz - извлечение картинок из игры S-Tilez
  • [ ] text/text-1 -

Варианты развития программ:

  • texture - переписать на Си, реализовать систему плагинов, добавить поддержку redfaction/vbm и daikatana/wal
  • view3d/v3dm - переписать на Си, реализовать систему плагинов, текстурирование моделей Quake 2, поиск и отделение независимых mesh'ей, их раскраску в разные цвета при просмотре
  • pup - отделить плагины от основной программы более явно
  • parled12 - переделать на libev, реализовать в виде модуля ядра Linux?
  • vview - переписать на Си и SDL, реализовать систему плагинов
  • wadview - переписать на Си и SDL

Правила работы с ветками gitflow

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

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

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

  • Ветки новых возможностей (feature branches). Ветки данного типа отщепляются от основной ветки develop, в них разрабатываются новые возможности (фичи) продукта. По завершении работы с новой возможностью ветка сливается в основную ветку develop. Рекомендуется именовать ветки единообразно. Например, использовать префикс (feature, bugfix, hotfix) в начале имени ветки и прямой слэш в качестве разделителя: feature/specific_name, feature/frontend/add_new_service и т. д.
  • Ветки релизов (release branches). Ветки данного типа отщепляются от основной ветки develop и используются для подготовки к выпуску релиза после разработки всех новых возможностей, в них можно исправлять ошибки, найденные при релизном тестировании. По мере исправления ошибок изменения из ветки релизов можно сливать в основную ветку develop. При выпуске релиза изменения из данной ветки сливаются в основную ветку master, а затем изменения из ветки master сливаются в ветку develop.
  • Ветки исправлений (hotfix branches). Ветки данного типа отщепляются от основной ветки master и позволяют внести быстрые исправления в релизную версию продукта, не затрагивая разработку следующих версий в основной ветке develop. По завершении работы ветка сливается в обе основные ветки (master и develop).
  • Ветки сопровождения проекта (support branches). Ветки данного типа отщепляются от основной ветки master и предназначены для одновременной поддержки нескольких предыдущих версий продукта. При этом в ветке master находится актуальная версия кода. Изменения из веток сопровождения проекта не сливаются в основные ветки.

Или более полное описание.

  • Разработка очередной версии компонентов:
    • ведется в ветке develop, в которую сливаются результаты работы feature/<ветка> или bugfix/<ветка>;
    • ветки bugfix/<ветка> или feature/<ветка> используются в качестве рабочих веток разработчиками;
    • результат доработки сливается в develop через pull-request;
    • при готовности релиз-кандидата выполняется слияние develop в ветку master через pull-request;
    • при выпуске релиза проставляется тег на commit, из которого выпускался релиз.
  • При необходимости внесения изменений в последний выпущенный релиз:
    • от тега релиза отщепляется ветка hotfix/<ветка>;
    • изменения выполняются непосредственно в этой ветке;
    • по готовности выполняется слияние hotfix/<ветка> в ветку master через pull-request и проставляется тег на commit, из которого выпускается релиз.
  • При необходимости внесения изменений в более старые релизы:
    • от тега соответствующего релиза отщепляется ветка support/<версия>;
    • для каждой доработки от support/<версия> отщепляются ветки feature/<ветка> или bugfix/<ветка>;
    • результат доработки сливается в support/<версия> через pull-request;
    • по готовности проставляется тег на commit в support/<версия>, из которого выпускается релиз.
  • Для заимствованных компонентов структура веток должна соответствовать Получение исходного кода заимствованного компонента из внешнего репозитория git
    • ветка vendor/master используется для синхронизации изменений с репозиторием разработчиков заимствованного компонента;
    • ветка vendor/master является родительской для остальных веток, которые содержат изменения, внесённые внутри компании(master, develop, support и всех остальных).

Удаление локальных и удалённых веток

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

$ git branch -d удаляемая-ветка

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

$ git push origin --delete удаляемая-ветка

Просмотр авторов изменений строк файла

Для просмотра построчного авторства файла исходного кода можно переключиться на интересующую ветку/фиксацию и ввести команду следующего вида:

$ git blame файл

Настройка стратегий слияния для отдельных файлов

Создаём в корне проекта файл .gitattributes, добавляем в него строки с названиями файлов (каталогов) и стратегиями, которые будут применяться к ним при слиянии, в следующем виде:

# при слиянии использовать только собственную версию файла
database.xml merge=ours

Клонирование удалённого репозитория

Есть общеупотребимый способ клонирования, когда клонируется ветка master в удалённом репозитории:

$ git clone <внешний репозиторий> <локальный репозиторий>

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

$ git clone --single-branch --branch <релизная ветка> <внешний репозиторий> <локальный репозиторий>

Загрузка ветки в удалённый репозиторий

Для заливки ветки в удалённый репозиторий origin под другим именем можно воспользоваться командой следующего вида:

$ git push origin <ветка>:<удалённая ветка>

Загрзука тега в удалённый репозиторий

Для загрузки тега в удалённый репозиторий origin под другим именем можно воспользоваться такой командой:

$ git push origin <тег>:<удалённый тег>

Посмотреть список тегов определённой ветки

Для просмотра списка тегов из определённой ветки, заданной по имени, тегу или идентификатору фиксации, вплоть до неё, можно воспользоваться такой командой:

$ git tag --merged <ветка>