#! /usr/bin/env pike |
|
mapping(string:program) hooks = ([ |
"pre-commit" : PreCommitHook, |
]); |
|
mapping(string:program) filters = ([ |
"nice_ident" : NiceIdentFilter, |
]); |
|
|
|
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); |
} |
|
array(string) split_z(string data) |
{ |
array(string) a = data / "\0"; |
if (sizeof(a) && a[-1] == "") |
a = a[..sizeof(a)-2]; |
return a; |
} |
|
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); |
} |
|
|
|
|
|
class PreCommitHook |
{ |
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); |
} |
|
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; |
} |
} |
|
int check_ident(string filename) |
{ |
string data = get_staged_file(filename); |
int p=0; |
while ((p = search(data, "$Id", p))>=0) { |
if (data[p..p+3] != "$Id$") { |
write("File %s contains an expanded ident.\n" |
"Try 'git reset %s; git add %s', " |
"or remove the ident manually.\n", |
@({filename})*3); |
return 1; |
} |
p += 4; |
} |
return 0; |
} |
|
int check_blocker_attributes(array(string) files_to_commit) |
{ |
constant attrs_to_check = ({ "foreign_ident", "block_commit", "ident" }); |
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; |
case "ident": |
if (value == "unset") |
break; |
if (check_ident(filename)) |
return 1; |
break; |
} |
} |
} |
return 0; |
} |
|
int hook() |
{ |
array(string) files_to_commit = |
split_z(run_git("diff", "--staged", "--name-only", "-z")); |
return |
check_attributes_staged() || |
check_blocker_attributes(files_to_commit); |
} |
} |
|
|
|
|
|
class NiceIdentFilter |
{ |
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) { |
|
f = f[..p-1]+r+f[p2+1..]; |
p += sizeof(r); |
} else { |
|
p = p2+1; |
} |
} else p += 3; |
} |
return f; |
} |
|
static string clean_ident(string i) |
{ |
if(has_prefix(i, "$Id:") && sizeof(i/" ")==13) |
return "$Id$"; |
} |
|
static string smudge_ident(string i) |
{ |
return "$Id$"; |
} |
|
int clean() |
{ |
write(replace_id(Stdio.stdin->read(), clean_ident)); |
return 0; |
} |
|
int smudge() |
{ |
write(replace_id(Stdio.stdin->read(), smudge_ident)); |
return 0; |
} |
} |
|
|
|
class GitHelper |
{ |
void setup_hooks() |
{ |
constant hooksdir = "hooks"; |
if (!sizeof(hooks)) |
return; |
if (!file_stat(hooksdir)) { |
write("Creating the hooks directory\n"); |
if (!mkdir(hooksdir)) |
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); |
} else if (s->islnk) { |
|
} else { |
write("Hook %s already exists, so won't overwrite it...\n", name); |
} |
} |
} |
|
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; |
if (old == "") { |
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) |
if (filter[op]) |
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; |
return 0; |
} |
|
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]) { |
werror("Filter %s does not implement %s!\n", filter, fop); |
return 1; |
} 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; |
} |
} |
|
|