Добавив это в свой сценарий update, мы запретим обновления, содержащие коммиты, сообщения которых не соблюдают наше правило.
Настройка системы контроля доступа для пользователей
Предположим, что нам хотелось бы добавить какой-нибудь механизм для использования списков контроля доступа (ACL), где указано, какие пользователи могут отправлять изменения и в какие части проекта. Несколько людей будут иметь полный доступ, а остальные будут иметь доступ на изменение только некоторых подкаталогов или отдельных файлов. Чтобы обеспечить выполнение такой политики, мы запишем правила в файл acl, который будет находиться в нашем "голом" репозитории на сервере. Нам нужно будет, чтобы перехватчик update брал эти правила, смотрел на то, какие файлы были изменены присланными коммитами, и определял, имеет ли пользователь, выполнивший push, право на обновление всех этих файлов.
Первое, что мы сделаем, — это напишем свой ACL. Мы сейчас будем использовать формат очень похожий на механизм ACL в CVS. В нём используется последовательность строк, где первое поле — это avail или unavail, следующее поле — это разделённый запятыми список пользователей, для которых применяется правило, и последнее поле — это путь, к которому применяется правило (пропуск здесь означает открытый доступ). Все эти поля разделяются вертикальной чертой (|).
В нашем примере будет несколько администраторов, сколько-то занимающихся написанием документации с доступом к каталогу doc и один разработчик, который имеет доступ только к каталогам lib и tests, и наш файл acl будет выглядеть так:
avail|nickh,pjhyett,defunkt,tpw avail|usinclair,cdickens,ebronte|doc avail|schacon|lib avail|schacon|tests
Начнём со считывания этих данных в какую-нибудь пригодную для использования структуру. В нашем случае, чтобы не усложнять пример, мы будем применять только директивы avail. Вот метод, который даёт нам ассоциативный массив, где ключом является имя пользователя, а значением — массив путей, для которых пользователь имеет доступ на запись:
def get_acl_access_data(acl_file) # считывание данных ACL acl_file = File.read(acl_file).split("\n").reject { |line| line == '' } access = {} acl_file.each do |line| avail, users, path = line.split('|') next unless avail == 'avail' users.split(',').each do |user| access[user] ||= [] access[user] << path end end access end
Для рассмотренного ранее ACL-файла, метод get_acl_access_data вернёт структуру данных следующего вида:
{"defunkt"=>[nil], "tpw"=>[nil], "nickh"=>[nil], "pjhyett"=>[nil], "schacon"=>["lib", "tests"], "cdickens"=>["doc"], "usinclair"=>["doc"], "ebronte"=>["doc"]}
Теперь, когда мы разобрались с правами, нам нужно выяснить, какие пути изменяются присланными коммитами, чтобы можно было убедиться, что пользователь, выполнивший push, имеет ко всем ним доступ.
Мы довольно легко можем определить, какие файлы были изменены в одном коммите, с помощью опции --name-only для команды git log (мы упоминали о ней в Главе 2):
$ git log -1 --name-only --pretty=format:'' 9f585d README lib/test.rb
Если мы воспользуемся ACL-структурой, полученной из метода get_acl_access_data, и сверим её со списком файлов для каждого коммита, то мы сможем определить, имеет ли пользователь право на отправку своих коммитов:
# некоторые подкаталоги в проекте разрешено модифицировать только определённым пользователям def check_directory_perms access = get_acl_access_data('acl') # проверим, что никто не пытается прислать чего-то, что ему нельзя new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n") new_commits.each do |rev| files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n") files_modified.each do |path| next if path.size == 0 has_file_access = false access[$user].each do |access_path| if !access_path # пользователь имеет полный доступ || (path.index(access_path) == 0) # доступ к этому пути has_file_access = true end end if !has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end end end end check_directory_perms
Большую часть этого кода должно быть не сложно понять. Мы получаем список присланных на сервер коммитов с помощью git rev-list. Затем для каждого из них мы узнаём, какие файлы были изменены, и убеждаемся, что пользователь, сделавший push, имеет доступ ко всем изменённым путям. Один Ruby'изм, который может быть непонятен это path.index(access_path) == 0. Это условие верно, если path начинается с access_path — оно гарантирует, что access_path это не просто один из разрешённых путей, а что каждый путь, к которому запрашивается доступ, начинается с одного из разрешённых путей.
Теперь наши пользователи не смогут отправить никаких коммитов с плохо отформатированными сообщениями и не смогут изменить файлы вне предназначенных для них путей.
Разрешение только обновлений-перемоток
Единственное, что нам осталось — это оставить доступными только обновления-перемотки. В Git версии 1.6 и более новых можно просто задать настройки receive.denyDeletes и receive.denyNonFastForwards. Но осуществление этого с помощью перехватчика будет работать и в старых версиях Git, и к тому же вы сможете изменить его так, чтобы запрет действовал только для определённых пользователей, или ещё как-то, как вам захочется.