githelper.git/
githelper.pike
Branch:
Tag:
Non-build tags
All tags
No tags
2010-09-25
2010-09-25 20:01:26 by Marcus Comstedt <marcus@mc.pp.se>
efde063c19bddfcbc429bf01f15f7eacf2ee8146 (
309
lines) (+
291
/-
18
)
[
Show
|
Annotate
]
Branch:
master
Server side hook.
2:
mapping(string:program) hooks = ([ "pre-commit" : PreCommitHook,
+
"pre-receive" : PreReceiveHook,
]); mapping(string:program) filters = ([
53:
return run_git_ex(0, @args); }
-
/* Hooks */
-
-
/* Checks run before editing a commit message */
-
-
class PreCommitHook
-
{
+
string get_staged_file(string filename) { string sha;
68:
return run_git("cat-file", "blob", sha); }
-
int check
_
attributes
_
staged
()
+
string
get
_
committed
_
file
(
string sha, string filename
)
{
-
//
We
don't
allow
.gitattributes
to
differ
between
wt
and
index,
-
//
because
that
could
mean
the
committed
stuff
ends
up
with different
-
//
attributes than they have right now...
+
string
blob;
+
string
attrentry
=
run_git("ls-tree",
sha,
"--",
filename);
+
if
(2
!=
sscanf(attrentry,
"%*o
blob
%s\t",
blob))
+
fail("Unexpected
output
from
git
ls-tree\n");
+
return
run_git("cat-file",
"blob",
blob);
+
}
-
if
(
sizeof(run
_
git
("
diff"
, "
--
name
-only"
,
".gitattributes"
))
)
{
-
write
(
"You
have
unstaged
changes
to
.gitattributes.
\n"
-
"Please
add
or
stash
them
before
commit.\n"
);
+
class
GitAttributes
+
{
+
enum
{
+
ATTR_TRUE = 1,
+
ATTR_FALSE = 2,
+
ATTR_UNSET = 3
+
};
+
+
class AttrState
(
string attr, string|int setto) {
+
static string
_
sprintf
(
int type) {
+
return type=='O' && sprintf(
"
AttrState(%O, %O)\n
",
attr, setto);
+
}
+
};
+
+
class MatchAttr(string
name,
int is_macro, array(AttrState
)
states
) {
+
static
string
_sprintf
(
int
type)
{
+
return type=='O' && sprintf("MatchAttr(%O, %d, %O)
\n"
,
+
name, is_macro, states);
+
}
+
};
+
+
static array(MatchAttr) attrs = ({});
+
+
static int invalid_attr_name(string name)
+
{
+
int n;
+
if(name ==
"
" || name[0] == '-'
)
return 1;
-
+
sscanf(name, "%*[-._0-9a-zA-Z]%n", n);
+
return n != sizeof(name);
}
-
+
+
static AttrState parse_attr(string src)
+
{
+
string equals = 0;
+
string|int setto;
+
sscanf(src, "%s=%s", src, equals);
+
if(src[0] == '-' || src[0] == '!') {
+
setto = (src[0]=='-'? ATTR_FALSE : ATTR_UNSET);
+
src = src[1..];
+
} else
+
setto = equals || ATTR_TRUE;
+
if(invalid_attr_name(src))
+
fail("%s is not a valid attribute name\n", src);
+
return AttrState(src, setto);
}
-
+
static MatchAttr parse_attr_line(string line, int macro_ok)
+
{
+
int is_macro=0;
+
string name;
+
line = String.trim_whites(replace(line, ({"\t","\r","\n"}),
+
({" ", " ", " "})));
+
if(!sizeof(line) || line[0] == '#')
+
return 0;
+
if(has_prefix(line, "[attr]")) {
+
if(!macro_ok)
+
fail("%s not allowed\n", name);
+
is_macro=1;
+
sscanf(line, "[attr]%*[ ]%s%*[ ]%s", name, line);
+
} else {
+
sscanf(line, "%s%*[ ]%s", name, line);
+
}
+
return MatchAttr(name, is_macro, map(line/" "-({""}), parse_attr));
+
}
+
+
static void handle_attr_line(string line, int macro_ok)
+
{
+
MatchAttr a = parse_attr_line(line, macro_ok);
+
if(a) attrs += ({ a });
+
}
+
+
static void create(string data)
+
{
+
foreach(data/"\n", string line)
+
handle_attr_line(line, 1);
+
}
+
+
static int path_matches(string path, string pattern)
+
{
+
if(search(pattern, "/")<0)
+
return glob(pattern, (path/"/")[-1]);
+
if(has_prefix(pattern, "/"))
+
pattern = pattern[1..];
+
return glob(pattern, path);
+
}
+
+
static void macroexpand_one(string attrname, array(MatchAttr) attrs,
+
mapping(string:string|int) all_attr)
+
{
+
MatchAttr a = 0;
+
if(all_attr[attrname] != ATTR_TRUE)
+
return;
+
for(int i=sizeof(attrs)-1; i>=0; --i)
+
if(attrs[i]->is_macro && attrs[i]->name == attrname)
+
a = attrs[i];
+
if(a) fill_one(a, attrs, all_attr);
+
}
+
+
static void fill_one(MatchAttr attr, array(MatchAttr) attrs,
+
mapping(string:string|int) all_attr)
+
{
+
for(int i = sizeof(attr->states)-1; i>=0; --i) {
+
AttrState s = attr->states[i];
+
if(!all_attr[s->attr]) {
+
all_attr[s->attr] = s->setto;
+
macroexpand_one(s->attr, attrs, all_attr);
+
}
+
}
+
}
+
+
static void fill(string path, array(MatchAttr) attrs,
+
mapping(string:string|int) all_attr)
+
{
+
for(int i=sizeof(attrs)-1; i>=0; --i)
+
if(!attrs[i]->is_macro && path_matches(path, attrs[i]->name))
+
fill_one(attrs[i], attrs, all_attr);
+
}
+
+
mapping(string:string|int) checkattr(string path)
+
{
+
mapping(string:string|int) all_attr = ([]);
+
fill(path, attrs, all_attr);
+
return all_attr;
+
}
+
+
static string _sprintf(int type) {
+
return type=='O' && sprintf("GitAttributes(%O)\n", attrs);
+
}
+
}
+
+
+
+
/* Hooks */
+
+
class CommitHookUtils
+
{
int find_expanded_ident(string data) { int p=0;
97:
return find_expanded_ident(get_staged_file(filename)); }
+
int find_expanded_ident_in_committed_file(string sha, string filename)
+
{
+
return find_expanded_ident(get_committed_file(sha, filename));
+
}
+
}
+
+
/* Checks run before editing a commit message */
+
+
class PreCommitHook
+
{
+
inherit CommitHookUtils;
+
+
int check_attributes_staged()
+
{
+
// We don't allow .gitattributes to differ between wt and index,
+
// because that could mean the committed stuff ends up with different
+
// attributes than they have right now...
+
+
if (sizeof(run_git("diff", "--name-only", ".gitattributes"))) {
+
write("You have unstaged changes to .gitattributes.\n"
+
"Please add or stash them before commit.\n");
+
return 1;
+
}
+
}
+
int check_ident(string filename) { if (find_expanded_ident_in_staged_file(filename)) {
187:
} }
+
/* Checks run before accepting a push */
+
+
class PreReceiveHook
+
{
+
inherit CommitHookUtils;
+
+
int check_ident(string sha, string filename)
+
{
+
if (find_expanded_ident_in_committed_file(sha, filename)) {
+
write("File %s contains an expanded ident.\n", filename);
+
return 1;
+
}
+
return 0;
+
}
+
+
int check_blocker_attributes(string sha, GitAttributes attrs, array(string) files_to_commit)
+
{
+
foreach(files_to_commit, string filename) {
+
mapping(string:string|int) a = attrs->checkattr(filename);
+
if(a->foreign_ident == GitAttributes.ATTR_TRUE) {
+
write("File %s has the foreign_ident attribute. Please remove it before commit.\n", filename);
+
return 1;
+
}
+
if(stringp(a->block_commit) || a->block_commit == GitAttributes.ATTR_TRUE) {
+
write("File %s is blocked from committing: %s\n", filename,
+
replace((stringp(a->block_commit)? a->block_commit :
+
"no explanation given"), "-", " "));
+
return 1;
+
}
+
if(a->ident && a->ident != GitAttributes.ATTR_FALSE &&
+
a->ident != GitAttributes.ATTR_UNSET) {
+
if (check_ident(sha, filename))
+
return 1;
+
}
+
}
+
return 0;
+
}
+
+
int check_gitattributes_files(string sha, array(string) files_to_commit)
+
{
+
foreach(files_to_commit, string filename)
+
if(has_suffix(filename, "/.gitattributes")) {
+
write(".gitattributes are not allowed in subdirectories\n");
+
return 1;
+
}
+
+
if(search(files_to_commit, ".gitattributes")>=0) {
+
string diff = run_git("diff", "-p", sha+"^", sha,
+
"--", ".gitattributes");
+
if (sizeof(diff)) {
+
int pos = search(diff, "\n@@");
+
if (pos >= 0)
+
diff = diff[pos+1..];
+
foreach(diff/"\n", string line)
+
if(sizeof(line) && search(line, "foreign_ident")>=0 &&
+
search(line, "[attr]") != 1 &&
+
(line[0]=='+' || line[0]=='-')) {
+
int code, len;
+
string fn;
+
if(sscanf(line, "%c/%s foreign_ident%n", code, fn, len) != 3 ||
+
len != sizeof(line)) {
+
write("Unsupported change of foreign_ident in .gitattributes\n");
+
return 1;
+
}
+
if (code=='-' && search(files_to_commit, fn)<0) {
+
write("Removed foreign_ident from unstaged file %s\n", fn);
+
return 1;
+
}
+
}
+
}
+
}
+
return 0;
+
}
+
+
int check_commit(string sha)
+
{
+
write("Checking commit %s\n", sha);
+
array(string) committed_files =
+
split_z(run_git("diff", "--name-only", "-z", sha, sha+"^"));
+
string attrentry = run_git("ls-tree", sha, "--", ".gitattributes");
+
string attrtext = "";
+
if (sizeof(attrentry)) {
+
string blob;
+
if (2 != sscanf(attrentry, "%*o blob %s\t", blob))
+
fail("Unexpected output from git ls-tree\n");
+
attrtext = run_git("cat-file", "blob", blob);
+
}
+
GitAttributes attrs = GitAttributes(attrtext);
+
return check_blocker_attributes(sha, attrs, committed_files) ||
+
check_gitattributes_files(sha, committed_files);
+
}
+
+
int check_push(string old_sha, string new_sha, string ref_name)
+
{
+
if(old_sha == "0"*40) {
+
// New ref, maybe check if the name is allowed...
+
return 0;
+
} else {
+
foreach(run_git("rev-list", old_sha+".."+new_sha)/"\n", string sha)
+
if(sizeof(sha) && check_commit(sha))
+
return 1;
+
return 0;
+
}
+
}
+
+
int hook()
+
{
+
foreach(Stdio.stdin->read() / "\n", string line)
+
if(sizeof(line)) {
+
array(string) args = line / " ";
+
if(sizeof(args) != 3)
+
fail("Unexpected input line to pre-receive hook: %s\n", line);
+
if(check_push(@args))
+
return 1;
+
}
+
return 0;
+
}
+
}
+
/* Filters */ /* A sample filter, not really useful... */