githelper.git
/
githelper.pike
version
»
Context lines:
10
20
40
80
file
none
3
githelper.git/githelper.pike:331:
/* Hooks */ class CommitHookUtils { static array(string) files_to_commit; GitAttributes attrs;
+
private string git_dir;
+
+
string get_git_dir()
+
{
+
return git_dir ||
+
(git_dir = String.trim_all_whites(run_git("rev-parse", "--git-dir")));
+
}
+
string get_file(string filename, int|void allow_empty); string get_old_file(string filename, int|void allow_empty); int entry_is_new(string filename) { return 0; } int find_expanded_ident(string data) { int p=0; while ((p = search(data, DOLLAR"Id", p))>=0) { if (data[p..p+3] != unexpanded_id) { int p2 = search(data, DOLLAR, p+3), p3 = search(data, "\n", p+3);
githelper.git/githelper.pike:535:
check_gitattributes_files(); } } /* Checks run before accepting a push */ class PreReceiveHook { inherit CommitHookUtilsRepo;
+
enum AccessLevel {
+
ACCESS_NONE = 0,
+
ACCESS_BASIC = 1,
+
ACCESS_FULL = 2, // rebase/delete branch, move/delete tag, etc.
+
};
+
+
static mapping(string:AccessLevel) groups = ([
+
"scratch": ACCESS_FULL,
+
]);
+
static array(string) commits_to_check = ({});
-
int
check
_
access
(string
ref_name, string
user)
+
static
void parse
_
groups
(string user)
{
-
/* Return 0 for no access, 1 for basic access, and 2 for full
-
access
(including rebase/delete branch, and move/delete tag) */
+
string git_dir = get_git_dir();
+
+
groups[user] = ACCESS_FULL;
+
+
string group_data = Stdio.read_bytes(combine_path(git_dir, "info
/
group"));
+
+
if (!group_data) return;
+
+
foreach(replace(group_data, "\r", "\n")/"\n", string line) {
+
array(string) fields = map(line/":", String.trim_all_whites);
+
+
// NB: We currently only care about fields 0 (group name)
+
// and 3 (member list). The first member of a group is
+
// considered the primary member, and has full access.
+
+
if ((sizeof(fields) != 4) || (fields[0] == "")) continue;
+
array(string) members = map(fields[3]/",", String.trim_all_whites);
+
if (members[0] == user) {
+
// Primary member.
+
groups[fields[0]] = ACCESS_FULL;
+
continue;
+
}
+
if (groups[fields[0]]) continue; // Already a member.
+
if (has_value(members, user)) {
+
groups[fields[0]] = ACCESS_BASIC;
+
continue;
+
}
+
groups[fields[0]] = ACCESS_NONE;
+
}
+
}
+
+
AccessLevel check_access(string ref_name, string user)
+
{
+
/
* Return
ACCESS_NONE (
0
)
for no access,
+
* ACCESS_BASIC (
1
)
for basic access, and
+
* ACCESS_FULL (
2
)
for full
access
+
*
(including rebase/delete branch, and move/delete tag)
+
*/
string shortref = ref_name; sscanf(shortref, "refs/%*[^/]/%s", shortref);
-
if
(
has_prefix(shortref,
"scratch/"
)
||
has_prefix(shortref,
user+
"/"))
-
return
2
;
+
foreach(groups; string group; AccessLevel ac) {
+
if (has_prefix(shortref,
group
+
"/"))
+
return
ac
;
+
}
if (search(ref_name, "/x-") >= 0) { write("The ref %s can only be modified by its owner\n", ref_name);
-
return
0
;
+
return
ACCESS_NONE
;
}
-
return
1
;
+
return
ACCESS_BASIC
;
} int check_tag_push(string old_sha, string new_sha, string ref_name,
-
int
access_level)
+
AccessLevel
access_level)
{
-
if (access_level >=
2
)
+
if (access_level >=
ACCESS_FULL
)
return 0; string oldtag = String.trim_all_whites(run_git_ex(1, "rev-parse", "--verify", "-q", ref_name)); if (sizeof(oldtag) && oldtag != new_sha) { write("Tag %s already exists with value %s, will not %s it\n", ref_name, oldtag, (new_sha == "0"*40? "delete":"move")); return 1; } if (!sizeof(oldtag) && search(ref_name[10..], "/") >= 0) { write("Common tags are not allowed to contain /.\n"); return 1; } return 0; } int check_branch_push(string old_sha, string new_sha, string ref_name,
-
int
access_level)
+
AccessLevel
access_level)
{ if (old_sha == "0"*40) { // New branch, check if the name is allowed... if (sscanf(ref_name, "refs/heads/%*[0-9.]%*c") < 2) { write("Main version branches can not be created remotely.\n"); return 1; } if (access_level < 2 && search(ref_name[11..], "/")>=0) { write("Common topic branch names are not allowed to contain /.\n"); return 1; } return 0; } else if (new_sha == "0"*40) { // Delete old branch if (access_level < 2) { write("You may not delete branches which do not belong to you.\n"); return 1; } return 0; } else {
-
if (access_level >=
2
)
+
if (access_level >=
ACCESS_FULL
)
/* Skip checks */ return 0; string merge_base = String.trim_all_whites(run_git("merge-base", old_sha, new_sha)); if (merge_base != old_sha) { write("Push to %s is not fast-forward.\n", ref_name); return 1; } array(string) old_depth = split_lf(run_git("rev-list", "--first-parent",
githelper.git/githelper.pike:623:
if (search(fp_path, old_sha)<0) { write("Commit %s does not contain %s in its first-parent ancestry.\nDid you pull with merge instead of rebase?\n", new_sha, old_sha); return 1; } commits_to_check += split_lf(run_git("rev-list", old_sha+".."+new_sha)); return 0; } }
-
int check_push(string old_sha, string new_sha, string ref_name)
+
int check_push(string
git_user,
+
string
old_sha, string new_sha, string ref_name)
{
-
string
git_user = getenv("GIT_USER")||getenv("USER")||"nobody";
-
int
access_level;
+
AccessLevel
access_level;
if(!(access_level = check_access(ref_name, git_user))) return 1; if (has_prefix(ref_name, "refs/tags/")) { return check_tag_push(old_sha, new_sha, ref_name, access_level); } else if (has_prefix(ref_name, "refs/heads/")) { return check_branch_push(old_sha, new_sha, ref_name, access_level); } else { write("Trying to push a ref which is neither under refs/tags/ or refs/heads/...\n"); return 1; } } int hook() {
-
+
string git_user = getenv("GIT_USER")||getenv("USER")||"nobody";
+
parse_groups(git_user);
foreach(split_lf(Stdio.stdin->read()), string line) { array(string) args = line / " "; if(sizeof(args) != 3) fail("Unexpected input line to pre-receive hook: %s\n", line);
-
if(check_push(@args))
+
if(check_push(
git_user,
@args))
return 1; } foreach(Array.uniq(commits_to_check), string sha) if(check_commit(sha)) return 1; return 0; } } /* Do housekeeping after a commit */