59cd49 | 2010-09-05 | Marcus Comstedt | | #! /usr/bin/env pike
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | mapping(string:program) hooks = ([
|
517918 | 2010-09-25 | Marcus Comstedt | | "pre-commit" : PreCommitHook,
|
efde06 | 2010-09-25 | Marcus Comstedt | | "pre-receive" : PreReceiveHook,
|
59cd49 | 2010-09-05 | Marcus Comstedt | | ]);
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | mapping(string:program) filters = ([
|
7b6b1b | 2010-09-25 | Marcus Comstedt | | #if 0
|
59cd49 | 2010-09-05 | Marcus Comstedt | | "nice_ident" : NiceIdentFilter,
|
7b6b1b | 2010-09-25 | Marcus Comstedt | | #endif
|
59cd49 | 2010-09-05 | Marcus Comstedt | | ]);
constant filterops = ({ "clean", "smudge" });
void fail(string msg, mixed ... args)
{
werror(msg, @args);
exit(1);
}
void iofailn(int errno, string msg, mixed ... args)
{
fail(msg+": %s\n", @args, strerror(errno));
}
void iofail(string msg, mixed ... args)
{
iofailn(errno(), msg, @args);
}
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | array(string) split_z(string data)
{
array(string) a = data / "\0";
if (sizeof(a) && a[-1] == "")
a = a[..sizeof(a)-2];
return a;
}
|
59cd49 | 2010-09-05 | Marcus Comstedt | | string run_git_ex(int max_exitcode, string ... args)
{
mapping res = Process.run(({"git"})+args);
if (res->exitcode > max_exitcode) {
werror(res->stderr);
fail("git exited with code %d\n", res->exitcode);
}
return res->stdout;
}
string run_git(string ... args)
{
return run_git_ex(0, @args);
}
|
efde06 | 2010-09-25 | Marcus Comstedt | | string get_staged_file(string filename)
{
string sha;
if (2 != sscanf(run_git("ls-files", "--stage", "--", filename),
"%*o %s ", sha))
fail("Unable to parse output from git ls-files...\n");
return run_git("cat-file", "blob", sha);
}
|
59cd49 | 2010-09-05 | Marcus Comstedt | |
|
aeab9a | 2010-09-25 | Marcus Comstedt | | string get_committed_file(string sha, string filename, int|void allow_empty)
|
efde06 | 2010-09-25 | Marcus Comstedt | | {
string blob;
string attrentry = run_git("ls-tree", sha, "--", filename);
|
aeab9a | 2010-09-25 | Marcus Comstedt | | if (allow_empty && !sizeof(attrentry))
return "";
|
efde06 | 2010-09-25 | Marcus Comstedt | | if (2 != sscanf(attrentry, "%*o blob %s\t", blob))
fail("Unexpected output from git ls-tree\n");
return run_git("cat-file", "blob", blob);
}
|
517918 | 2010-09-25 | Marcus Comstedt | |
|
efde06 | 2010-09-25 | Marcus Comstedt | | class GitAttributes
|
59cd49 | 2010-09-05 | Marcus Comstedt | | {
|
efde06 | 2010-09-25 | Marcus Comstedt | | 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)
|
08686f | 2010-09-25 | Marcus Comstedt | | {
|
efde06 | 2010-09-25 | Marcus Comstedt | | int n;
if(name == "" || name[0] == '-')
return 1;
sscanf(name, "%*[-._0-9a-zA-Z]%n", n);
return n != sizeof(name);
|
08686f | 2010-09-25 | Marcus Comstedt | | }
|
efde06 | 2010-09-25 | Marcus Comstedt | | static AttrState parse_attr(string src)
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | {
|
efde06 | 2010-09-25 | Marcus Comstedt | | 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);
}
|
0c9c48 | 2010-09-25 | Marcus Comstedt | |
|
efde06 | 2010-09-25 | Marcus Comstedt | | 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);
}
|
19bf73 | 2010-09-26 | Marcus Comstedt | | return MatchAttr(name, is_macro, reverse(map(line/" "-({""}), parse_attr)));
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
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);
|
19bf73 | 2010-09-26 | Marcus Comstedt | | attrs = reverse(attrs);
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
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)
{
|
19bf73 | 2010-09-26 | Marcus Comstedt | | MatchAttr ma = 0;
|
efde06 | 2010-09-25 | Marcus Comstedt | | if(all_attr[attrname] != ATTR_TRUE)
return;
|
19bf73 | 2010-09-26 | Marcus Comstedt | | foreach(attrs, MatchAttr a)
if(a->is_macro && a->name == attrname)
ma = a;
if(ma) fill_one(ma, attrs, all_attr);
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
static void fill_one(MatchAttr attr, array(MatchAttr) attrs,
mapping(string:string|int) all_attr)
{
|
19bf73 | 2010-09-26 | Marcus Comstedt | | foreach(attr->states, AttrState s) {
|
efde06 | 2010-09-25 | Marcus Comstedt | | if(!all_attr[s->attr]) {
all_attr[s->attr] = s->setto;
macroexpand_one(s->attr, attrs, all_attr);
}
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | }
}
|
efde06 | 2010-09-25 | Marcus Comstedt | | static void fill(string path, array(MatchAttr) attrs,
mapping(string:string|int) all_attr)
{
|
19bf73 | 2010-09-26 | Marcus Comstedt | | foreach(attrs, MatchAttr a)
if(!a->is_macro && path_matches(path, a->name))
fill_one(a, attrs, all_attr);
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
mapping(string:string|int) checkattr(string path)
{
mapping(string:string|int) all_attr = ([]);
fill(path, attrs, all_attr);
return all_attr;
}
|
aeab9a | 2010-09-25 | Marcus Comstedt | | array(string) findattr(string attrname)
{
array(string) r = ({});
foreach(attrs, MatchAttr attr) {
int z=0;
foreach(attr->states, AttrState state)
if(state->attr == attrname && state->setto == ATTR_TRUE) {
z = 1;
break;
}
if (z)
r += ({ attr->name });
}
return r;
}
|
efde06 | 2010-09-25 | Marcus Comstedt | | static string _sprintf(int type) {
return type=='O' && sprintf("GitAttributes(%O)\n", attrs);
}
}
class CommitHookUtils
{
|
d64580 | 2010-09-26 | Marcus Comstedt | | static array(string) files_to_commit;
string get_file(string filename);
|
0ef2fc | 2010-09-25 | Marcus Comstedt | | int find_expanded_ident(string data)
|
08686f | 2010-09-25 | Marcus Comstedt | | {
int p=0;
while ((p = search(data, "$Id", p))>=0) {
|
0ef2fc | 2010-09-25 | Marcus Comstedt | | if (data[p..p+3] != "$Id$")
return 1;
p += 4;
}
return 0;
}
|
efde06 | 2010-09-25 | Marcus Comstedt | |
|
d64580 | 2010-09-26 | Marcus Comstedt | | int check_ident(string filename)
|
efde06 | 2010-09-25 | Marcus Comstedt | | {
|
d64580 | 2010-09-26 | Marcus Comstedt | | if (find_expanded_ident(get_file(filename))) {
write("File %s contains an expanded ident.\n", filename);
if(this_program == PreCommitHook) {
write("Try 'git reset %s; git add %s', "
"or remove the ident manually.\n",
@({filename})*2);;
}
return 1;
}
return 0;
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
}
class PreCommitHook
{
inherit CommitHookUtils;
|
d64580 | 2010-09-26 | Marcus Comstedt | | string get_file(string filename)
{
return get_staged_file(filename);
}
|
efde06 | 2010-09-25 | Marcus Comstedt | | int check_attributes_staged()
{
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;
}
}
|
0ef2fc | 2010-09-25 | Marcus Comstedt | |
|
d64580 | 2010-09-26 | Marcus Comstedt | | int check_blocker_attributes()
|
59cd49 | 2010-09-05 | Marcus Comstedt | | {
|
08686f | 2010-09-25 | Marcus Comstedt | | constant attrs_to_check = ({ "foreign_ident", "block_commit", "ident" });
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | foreach(run_git("check-attr", @attrs_to_check,
"--", @files_to_commit) / "\n" - ({""}),
string line) {
array(string) parts = line / ": ";
if (sizeof(parts) != 3)
fail("Unexpected output from git check-attr, please fix check_blocker_attributes()\n");
[string filename, string attribute, string value] = parts;
if (value != "unspecified") {
switch (attribute) {
case "foreign_ident":
write("File %s has the foreign_ident attribute. Please remove it before commit.\n", filename);
return 1;
case "block_commit":
write("File %s is blocked from committing: %s\n", filename,
replace(value, "-", " "));
return 1;
|
08686f | 2010-09-25 | Marcus Comstedt | | case "ident":
if (value == "unset")
break;
if (check_ident(filename))
return 1;
break;
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | }
}
}
|
80f91d | 2010-09-05 | Marcus Comstedt | | return 0;
|
59cd49 | 2010-09-05 | Marcus Comstedt | | }
|
0c9c48 | 2010-09-25 | Marcus Comstedt | |
|
d64580 | 2010-09-26 | Marcus Comstedt | | int check_gitattributes_files()
|
0ef2fc | 2010-09-25 | Marcus Comstedt | | {
foreach(files_to_commit, string filename)
|
1b0092 | 2010-09-25 | Marcus Comstedt | | 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", "--cached",
"--", ".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 &&
|
a10aeb | 2010-09-25 | Marcus Comstedt | | search(line, "[attr]") != 1 &&
|
1b0092 | 2010-09-25 | Marcus Comstedt | | (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;
}
|
0ef2fc | 2010-09-25 | Marcus Comstedt | | }
|
1b0092 | 2010-09-25 | Marcus Comstedt | | }
}
|
0ef2fc | 2010-09-25 | Marcus Comstedt | | return 0;
}
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | int hook()
{
|
d64580 | 2010-09-26 | Marcus Comstedt | | files_to_commit =
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | split_z(run_git("diff", "--staged", "--name-only", "-z"));
return
check_attributes_staged() ||
|
d64580 | 2010-09-26 | Marcus Comstedt | | check_blocker_attributes() ||
check_gitattributes_files();
|
0c9c48 | 2010-09-25 | Marcus Comstedt | | }
|
59cd49 | 2010-09-05 | Marcus Comstedt | | }
|
efde06 | 2010-09-25 | Marcus Comstedt | |
class PreReceiveHook
{
inherit CommitHookUtils;
|
d64580 | 2010-09-26 | Marcus Comstedt | | static string sha;
string get_file(string filename)
|
efde06 | 2010-09-25 | Marcus Comstedt | | {
|
d64580 | 2010-09-26 | Marcus Comstedt | | return get_committed_file(sha, filename);
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
|
d64580 | 2010-09-26 | Marcus Comstedt | | int check_blocker_attributes(GitAttributes attrs)
|
efde06 | 2010-09-25 | Marcus Comstedt | | {
foreach(files_to_commit, string filename) {
mapping(string:string|int) a = attrs->checkattr(filename);
if(a->foreign_ident == GitAttributes.ATTR_TRUE) {
|
aeab9a | 2010-09-25 | Marcus Comstedt | | if (sizeof(run_git("ls-tree", sha+"^", "--", filename)) ||
!sizeof(run_git("ls-tree", sha, "--", filename))) {
write("File %s has the foreign_ident attribute. Please remove it before commit.\n", filename);
return 1;
}
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
if(stringp(a->block_commit) || a->block_commit == GitAttributes.ATTR_TRUE) {
|
aeab9a | 2010-09-25 | Marcus Comstedt | | if (sizeof(run_git("ls-tree", sha+"^", "--", filename)) ||
!sizeof(run_git("ls-tree", sha, "--", filename))) {
write("File %s is blocked from committing: %s\n", filename,
replace((stringp(a->block_commit)? a->block_commit :
"no explanation given"), "-", " "));
|
efde06 | 2010-09-25 | Marcus Comstedt | | return 1;
|
aeab9a | 2010-09-25 | Marcus Comstedt | | }
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
if(a->ident && a->ident != GitAttributes.ATTR_FALSE &&
a->ident != GitAttributes.ATTR_UNSET) {
|
d64580 | 2010-09-26 | Marcus Comstedt | | if (check_ident(filename))
|
efde06 | 2010-09-25 | Marcus Comstedt | | return 1;
}
}
return 0;
}
|
d64580 | 2010-09-26 | Marcus Comstedt | | int check_gitattributes_files(GitAttributes attrs)
|
efde06 | 2010-09-25 | Marcus Comstedt | | {
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) {
|
aeab9a | 2010-09-25 | Marcus Comstedt | | GitAttributes old_attrs =
GitAttributes(get_committed_file(sha+"^", ".gitattributes", 1));
array(string) new_f_e = sort(attrs->findattr("foreign_ident"));
array(string) old_f_e = sort(old_attrs->findattr("foreign_ident"));
array(string) added_fe = new_f_e - old_f_e;
array(string) removed_fe = old_f_e - new_f_e;
foreach(added_fe, string path) {
if(!has_prefix(path, "/") || search(path, "*")>=0) {
write("Added unsupported foreign_ident: %s\n", path);
return 1;
}
path = path[1..];
if (sizeof(run_git("ls-tree", sha+"^", "--", path)) ||
!sizeof(run_git("ls-tree", sha, "--", path))) {
write("Added foreign_ident to unadded file %s\n", path);
return 1;
}
}
foreach(removed_fe, string path) {
if(has_prefix(path, "/"))
path = path[1..];
if (search(files_to_commit, path)<0) {
write("Removed foreign_ident from unchanged file %s\n", path);
return 1;
}
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
}
return 0;
}
int check_commit(string sha)
{
write("Checking commit %s\n", sha);
|
d64580 | 2010-09-26 | Marcus Comstedt | | this_program::sha = sha;
files_to_commit =
|
efde06 | 2010-09-25 | Marcus Comstedt | | split_z(run_git("diff", "--name-only", "-z", sha, sha+"^"));
|
aeab9a | 2010-09-25 | Marcus Comstedt | | string attrtext = get_committed_file(sha, ".gitattributes", 1);
|
efde06 | 2010-09-25 | Marcus Comstedt | | GitAttributes attrs = GitAttributes(attrtext);
|
d64580 | 2010-09-26 | Marcus Comstedt | | return check_blocker_attributes(attrs) ||
check_gitattributes_files(attrs);
|
efde06 | 2010-09-25 | Marcus Comstedt | | }
int check_push(string old_sha, string new_sha, string ref_name)
{
if(old_sha == "0"*40) {
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;
}
}
|
59cd49 | 2010-09-05 | Marcus Comstedt | |
|
517918 | 2010-09-25 | Marcus Comstedt | |
|
59cd49 | 2010-09-05 | Marcus Comstedt | | class NiceIdentFilter
{
|
80f91d | 2010-09-05 | Marcus Comstedt | | static string replace_id(string f, function(string:string) replace) {
int p=0;
while((p=search(f, "$Id", p)) >= 0) {
int p2 = search(f, "$", p+3), p3 = search(f, "\n", p+3);
if (p2 > p && p2 < p3) {
string r = replace(f[p..p2]);
if (r) {
|
594051 | 2010-09-05 | Marcus Comstedt | |
|
80f91d | 2010-09-05 | Marcus Comstedt | | f = f[..p-1]+r+f[p2+1..];
p += sizeof(r);
|
594051 | 2010-09-05 | Marcus Comstedt | | } else {
p = p2+1;
}
|
80f91d | 2010-09-05 | Marcus Comstedt | | } else p += 3;
}
return f;
}
static string clean_ident(string i)
{
|
594051 | 2010-09-05 | Marcus Comstedt | | if(has_prefix(i, "$Id:") && sizeof(i/" ")==13)
|
80f91d | 2010-09-05 | Marcus Comstedt | | return "$Id$";
}
static string smudge_ident(string i)
{
return "$Id$";
}
int clean()
|
59cd49 | 2010-09-05 | Marcus Comstedt | | {
|
80f91d | 2010-09-05 | Marcus Comstedt | | write(replace_id(Stdio.stdin->read(), clean_ident));
return 0;
|
59cd49 | 2010-09-05 | Marcus Comstedt | | }
|
c1f54a | 2010-09-05 | Marcus Comstedt | | int smudge()
|
59cd49 | 2010-09-05 | Marcus Comstedt | | {
|
80f91d | 2010-09-05 | Marcus Comstedt | | write(replace_id(Stdio.stdin->read(), smudge_ident));
return 0;
|
59cd49 | 2010-09-05 | Marcus Comstedt | | }
}
class GitHelper
{
void setup_hooks()
{
constant hooksdir = "hooks";
if (!sizeof(hooks))
return;
if (!file_stat(hooksdir)) {
write("Creating the hooks directory\n");
|
52d815 | 2010-09-05 | Marcus Comstedt | | if (!mkdir(hooksdir))
|
59cd49 | 2010-09-05 | Marcus Comstedt | | iofail("Failed to create %s", hooksdir);
}
foreach (hooks; string name; ) {
string path = combine_path(hooksdir, name);
Stdio.Stat s = file_stat(path, 1);
if (!s) {
write("Installing %s\n", path);
System.symlink(__FILE__, path);
|
52d815 | 2010-09-05 | Marcus Comstedt | | } else if (s->islnk) {
|
59cd49 | 2010-09-05 | Marcus Comstedt | |
} else {
|
517918 | 2010-09-25 | Marcus Comstedt | | write("Hook %s already exists, so won't overwrite it...\n", name);
|
59cd49 | 2010-09-05 | Marcus Comstedt | | }
}
}
void setup_filter(string name, string op)
{
string confname = "filter."+name+"."+op;
string old = String.trim_all_whites(run_git_ex(1, "config", "--get", confname));
string cmd = __FILE__+" filter_"+name+"_"+op;
|
52d815 | 2010-09-05 | Marcus Comstedt | | if (old == "") {
|
59cd49 | 2010-09-05 | Marcus Comstedt | | write("Installing filter operation %s\n", confname);
run_git("config", confname, cmd);
} else if(old == cmd) {
} else {
write("Filter operation %s is already set to %s, not modifying\n",
confname, old);
}
}
void setup_filters()
{
foreach (filters; string name; program fprog) {
object filter = fprog();
foreach (filterops; ; string op)
|
52d815 | 2010-09-05 | Marcus Comstedt | | if (filter[op])
|
59cd49 | 2010-09-05 | Marcus Comstedt | | setup_filter(name, op);
}
}
int setup(array(string) args)
{
if (sizeof(args)) {
werror("githelper.pike should be invoked without arguments...\n");
return 1;
}
if (!cd(String.trim_all_whites(run_git("rev-parse", "--git-dir"))))
iofail("Failed to cd to .git directory");
setup_hooks();
setup_filters();
return 0;
}
}
string get_filter_op(string arg)
{
if (!has_prefix(arg, "filter_"))
return 0;
foreach (filterops; ; string op)
if (has_suffix(arg, "_"+op))
return op;
|
070acd | 2010-09-05 | Marcus Comstedt | | return 0;
|
59cd49 | 2010-09-05 | Marcus Comstedt | | }
int main(int argc, array(string) argv)
{
string command_name = basename(argv[0]);
if (hooks[command_name])
return hooks[command_name]()->hook(@argv[1..]);
else if (command_name == "githelper.pike") {
string fop;
if (argc>1 && (fop = get_filter_op(argv[1]))) {
string filter = argv[1][7..sizeof(argv[1])-(sizeof(fop)+2)];
if (filters[filter]) {
object f = filters[filter]();
if (!f[fop]) {
|
52d815 | 2010-09-05 | Marcus Comstedt | | werror("Filter %s does not implement %s!\n", filter, fop);
return 1;
|
59cd49 | 2010-09-05 | Marcus Comstedt | | } else
return f[fop](@argv[2..]);
} else {
werror("Unknown filter %s!\n", filter);
return 1;
}
} else
return GitHelper()->setup(argv[1..]);
} else {
werror("Unknown invocation method %s!\n", command_name);
return 1;
}
}
|