defget_acl_access_data(acl_file)
# read in ACL data
acl_file = File.read(acl_file).split( " \n" ).reject { |line| line == '' }
access = {}
acl_file.each do|line|
avail, users, path = line.split( '|' )
next unlessavail == 'avail'
users.split( ',' ).each do|user|
access[user] ||= []
access[user] << path
end
end
access
end
On the ACL file you looked at earlier, this get_acl_access_data method returns a data structure that looks like this:
{ "defunkt" =>[ nil],
"tpw" =>[ nil],
"nickh" =>[ nil],
"pjhyett" =>[ nil],
"schacon" =>[ "lib" , "tests" ],
"cdickens" =>[ "doc" ],
"usinclair" =>[ "doc" ],
"ebronte" =>[ "doc" ]}
Now that you have the permissions sorted out, you need to determine what paths the commits being pushed have modified, so you can make sure the user who’s pushing has access to all of them.
You can pretty easily see what files have been modified in a single commit with the --name-only option to the git log command (mentioned briefly in Chapter 2):
$git log -1 --name-only --pretty=format: '' 9f585d
README
lib/test.rb
If you use the ACL structure returned from the get_acl_access_data method and check it against the listed files in each of the commits, you can determine whether the user has access to push all of their commits:
# only allows certain users to modify certain subdirectories in a project
defcheck_directory_perms
access = get_acl_access_data( 'acl' )
# see if anyone is trying to push something they can't
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 ifpath.size == 0
has_file_access = false
access[$user].each do|access_path|
if!access_path # user has access to everything
|| (path.start_with? access_path) # access to this path
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
You get a list of new commits being pushed to your server with git rev-list. Then, for each of those commits, you find which files are modified and make sure the user who’s pushing has access to all the paths being modified.
Now your users can’t push any commits with badly formed messages or with modified files outside of their designated paths.
If you run chmod u+x .git/hooks/update, which is the file into which you should have put all this code, and then try to push a commit with a non-compliant message, you get something like this:
$git push -f origin master
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 323 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
Enforcing Policies...
(refs/heads/master) (8338c5) (c5b616)
[POLICY] Your message is not formatted correctly
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/master
To git@gitserver:project.git
! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'git@gitserver:project.git'
There are a couple of interesting things here. First, you see this where the hook starts running.
Enforcing Policies...
(refs/heads/master) (fb8c72) (c56860)
Remember that you printed that out at the very beginning of your update script. Anything your script echoes to stdout will be transferred to the client.
The next thing you’ll notice is the error message.
[POLICY] Your message is not formatted correctly
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/master
The first line was printed out by you, the other two were Git telling you that the update script exited non-zero and that is what is declining your push. Lastly, you have this:
To git@gitserver:project.git
! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'git@gitserver:project.git'
You’ll see a remote rejected message for each reference that your hook declined, and it tells you that it was declined specifically because of a hook failure.
Furthermore, if someone tries to edit a file they don’t have access to and push a commit containing it, they will see something similar. For instance, if a documentation author tries to push a commit modifying something in the lib directory, they see
[POLICY] You do not have access to push to lib/test.rb
From now on, as long as that update script is there and executable, your repository will never have a commit message without your pattern in it, and your users will be sandboxed.
The downside to this approach is the whining that will inevitably result when your users' commit pushes are rejected. Having their carefully crafted work rejected at the last minute can be extremely frustrating and confusing; and furthermore, they will have to edit their history to correct it, which isn’t always for the faint of heart.
The answer to this dilemma is to provide some client-side hooks that users can run to notify them when they’re doing something that the server is likely to reject. That way, they can correct any problems before committing and before those issues become more difficult to fix. Because hooks aren’t transferred with a clone of a project, you must distribute these scripts some other way and then have your users copy them to their .git/hooks directory and make them executable. You can distribute these hooks within the project or in a separate project, but Git won’t set them up automatically.
To begin, you should check your commit message just before each commit is recorded, so you know the server won’t reject your changes due to badly formatted commit messages. To do this, you can add the commit-msg hook. If you have it read the message from the file passed as the first argument and compare that to the pattern, you can force Git to abort the commit if there is no match:
#!/usr/bin/env ruby
message_file = ARGV[0]
message = File.read(message_file)
$regex = /\[ref: (\d+)\]/
if!$regex.match(message)
puts "[POLICY] Your message is not formatted correctly"
exit 1
end
If that script is in place (in .git/hooks/commit-msg) and executable, and you commit with a message that isn’t properly formatted, you see this:
Читать дальше