$git commit -am 'test'
[POLICY] Your message is not formatted correctly
No commit was completed in that instance. However, if your message contains the proper pattern, Git allows you to commit:
$git commit -am 'test [ref: 132]'
[master e05c914] test [ref: 132]
1 file changed, 1 insertions(+), 0 deletions(-)
Next, you want to make sure you aren’t modifying files that are outside your ACL scope. If your project’s .git directory contains a copy of the ACL file you used previously, then the following pre-commit script will enforce those constraints for you:
#!/usr/bin/env ruby
$user = ENV[ 'USER' ]
# [ insert acl_access_data method from above ]
# only allows certain users to modify certain subdirectories in a project
defcheck_directory_perms
access = get_acl_access_data( '.git/acl' )
files_modified = `git diff-index --cached --name-only HEAD` .split( " \n" )
files_modified.each do|path|
next ifpath.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
if!has_file_access
puts "[POLICY] You do not have access to push to #{ path }"
exit 1
end
end
end
check_directory_perms
This is roughly the same script as the server-side part, but with two important differences. First, the ACL file is in a different place, because this script runs from your working directory, not from your .git directory. You have to change the path to the ACL file from this
access = get_acl_access_data( 'acl' )
to this:
access = get_acl_access_data( '.git/acl' )
The other important difference is the way you get a listing of the files that have been changed. Because the server-side method looks at the log of commits, and, at this point, the commit hasn’t been recorded yet, you must get your file listing from the staging area instead. Instead of
files_modified = `git log -1 --name-only --pretty=format:'' #{ ref }`
you have to use
files_modified = `git diff-index --cached --name-only HEAD`
But those are the only two differences – otherwise, the script works the same way. One caveat is that it expects you to be running locally as the same user you push as to the remote machine. If that is different, you must set the $user variable manually.
One other thing we can do here is make sure the user doesn’t push non-fast-forwarded references. To get a reference that isn’t a fast-forward, you either have to rebase past a commit you’ve already pushed up or try pushing a different local branch up to the same remote branch.
Presumably, the server is already configured with receive.denyDeletes and receive.denyNonFastForwards to enforce this policy, so the only accidental thing you can try to catch is rebasing commits that have already been pushed.
Here is an example pre-rebase script that checks for that. It gets a list of all the commits you’re about to rewrite and checks whether they exist in any of your remote references. If it sees one that is reachable from one of your remote references, it aborts the rebase.
#!/usr/bin/env ruby
base_branch = ARGV[0]
ifARGV[1]
topic_branch = ARGV[1]
else
topic_branch = "HEAD"
end
target_shas = `git rev-list #{ base_branch }.. #{ topic_branch }` .split( " \n" )
remote_refs = `git branch -r` .split( " \n" ).map { |r| r.strip }
target_shas.each do|sha|
remote_refs.each do|remote_ref|
shas_pushed = `git rev-list ^ #{ sha }^@ refs/remotes/ #{ remote_ref }`
ifshas_pushed.split( " \n" ).include?(sha)
puts "[POLICY] Commit #{ sha }has already been pushed to #{ remote_ref }"
exit 1
end
end
end
This script uses a syntax that wasn’t covered in the Revision Selection section of Chapter 6. You get a list of commits that have already been pushed up by running this:
`git rev-list ^ #{ sha }^@ refs/remotes/ #{ remote_ref }`
The SHA^@ syntax resolves to all the parents of that commit. You’re looking for any commit that is reachable from the last commit on the remote and that isn’t reachable from any parent of any of the SHA-1s you’re trying to push up – meaning it’s a fast-forward.
The main drawback to this approach is that it can be very slow and is often unnecessary – if you don’t try to force the push with -f, the server will warn you and not accept the push. However, it’s an interesting exercise and can in theory help you avoid a rebase that you might later have to go back and fix.
Мы рассмотрели большинство основных способов настройки клиента и сервера Git’а с тем, чтобы он был максимально удобен для ваших проектов и при вашей организации рабочего процесса. Мы узнали о всевозможных настройках, атрибутах файлов и о перехватчиках событий, а также рассмотрели пример настройки сервера с соблюдением политики. Теперь вам должно быть по плечу заставить Git подстроиться под практически любой тип рабочего процесса, который можно вообразить.
ГЛАВА 9. GIT И ДРУГИЕ СИСТЕМЫ КОНТРОЛЯ ВЕРСИЙ
Наш мир несовершенен. Как правило, вы не сможете быстро перевести проект, в котором вы участвуете, на использование Git. Иногда вам придётся иметь дело с проектами, использующими другую систему контроля версий, хотя вам и не нравится, что это не Git. В первой части этого раздела вы узнаете о способах использования Git в качестве клиента для работы с проектом, размещенном в какой-то другой системе.
В какой-то момент, вы, возможно, захотите перевести свой существующий проект на Git. Во второй части раздела вы узнаете о том, как провести миграцию с некоторых распространённых систем, а также познакомитесь с методом, который будет работать в нестандартных ситуациях, когда готовых инструментов миграции не существует.
Читать дальше