59cd492010-09-05Marcus Comstedt #! /usr/bin/env pike
0c9c482010-09-25Marcus Comstedt mapping(string:program) hooks = ([
5179182010-09-25Marcus Comstedt  "pre-commit" : PreCommitHook,
59cd492010-09-05Marcus Comstedt ]);
0c9c482010-09-25Marcus Comstedt mapping(string:program) filters = ([
7b6b1b2010-09-25Marcus Comstedt #if 0
59cd492010-09-05Marcus Comstedt  "nice_ident" : NiceIdentFilter,
7b6b1b2010-09-25Marcus Comstedt #endif
59cd492010-09-05Marcus 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); }
0c9c482010-09-25Marcus Comstedt array(string) split_z(string data) { array(string) a = data / "\0"; if (sizeof(a) && a[-1] == "") a = a[..sizeof(a)-2]; return a; }
59cd492010-09-05Marcus 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); } /* Hooks */
5179182010-09-25Marcus Comstedt /* Checks run before editing a commit message */ class PreCommitHook
59cd492010-09-05Marcus Comstedt {
08686f2010-09-25Marcus 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); }
0c9c482010-09-25Marcus Comstedt  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; } }
0ef2fc2010-09-25Marcus Comstedt  int find_expanded_ident(string data)
08686f2010-09-25Marcus Comstedt  { int p=0; while ((p = search(data, "$Id", p))>=0) {
0ef2fc2010-09-25Marcus Comstedt  if (data[p..p+3] != "$Id$") return 1; p += 4; } return 0; } int find_expanded_ident_in_staged_file(string filename) { return find_expanded_ident(get_staged_file(filename)); } int check_ident(string filename) { if (find_expanded_ident_in_staged_file(filename)) {
08686f2010-09-25Marcus Comstedt  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; } return 0; }
0c9c482010-09-25Marcus Comstedt  int check_blocker_attributes(array(string) files_to_commit)
59cd492010-09-05Marcus Comstedt  {
08686f2010-09-25Marcus Comstedt  constant attrs_to_check = ({ "foreign_ident", "block_commit", "ident" });
0c9c482010-09-25Marcus 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;
08686f2010-09-25Marcus Comstedt  case "ident": if (value == "unset") break; if (check_ident(filename)) return 1; break;
0c9c482010-09-25Marcus Comstedt  } } }
80f91d2010-09-05Marcus Comstedt  return 0;
59cd492010-09-05Marcus Comstedt  }
0c9c482010-09-25Marcus Comstedt 
0ef2fc2010-09-25Marcus Comstedt  int check_gitattributes_files(array(string) files_to_commit) { foreach(files_to_commit, string filename)
1b00922010-09-25Marcus 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 &&
a10aeb2010-09-25Marcus Comstedt  search(line, "[attr]") != 1 &&
1b00922010-09-25Marcus 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; }
0ef2fc2010-09-25Marcus Comstedt  }
1b00922010-09-25Marcus Comstedt  } }
0ef2fc2010-09-25Marcus Comstedt  return 0; }
0c9c482010-09-25Marcus Comstedt  int hook() { array(string) files_to_commit = split_z(run_git("diff", "--staged", "--name-only", "-z")); return check_attributes_staged() ||
0ef2fc2010-09-25Marcus Comstedt  check_blocker_attributes(files_to_commit) || check_gitattributes_files(files_to_commit);
0c9c482010-09-25Marcus Comstedt  }
59cd492010-09-05Marcus Comstedt } /* Filters */
5179182010-09-25Marcus Comstedt /* A sample filter, not really useful... */
59cd492010-09-05Marcus Comstedt class NiceIdentFilter {
80f91d2010-09-05Marcus 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) {
5940512010-09-05Marcus Comstedt  // werror("Replacing %O with %O\n", f[p..p2], r);
80f91d2010-09-05Marcus Comstedt  f = f[..p-1]+r+f[p2+1..]; p += sizeof(r);
5940512010-09-05Marcus Comstedt  } else { // werror("Not replacing %O\n", f[p..p2]); p = p2+1; }
80f91d2010-09-05Marcus Comstedt  } else p += 3; } return f; } static string clean_ident(string i) {
5940512010-09-05Marcus Comstedt  if(has_prefix(i, "$Id:") && sizeof(i/" ")==13)
80f91d2010-09-05Marcus Comstedt  return "$Id$"; } static string smudge_ident(string i) { return "$Id$"; } int clean()
59cd492010-09-05Marcus Comstedt  {
80f91d2010-09-05Marcus Comstedt  write(replace_id(Stdio.stdin->read(), clean_ident)); return 0;
59cd492010-09-05Marcus Comstedt  }
c1f54a2010-09-05Marcus Comstedt  int smudge()
59cd492010-09-05Marcus Comstedt  {
80f91d2010-09-05Marcus Comstedt  write(replace_id(Stdio.stdin->read(), smudge_ident)); return 0;
59cd492010-09-05Marcus Comstedt  } } /* Main helper */ class GitHelper { void setup_hooks() { constant hooksdir = "hooks"; if (!sizeof(hooks)) return; if (!file_stat(hooksdir)) { write("Creating the hooks directory\n");
52d8152010-09-05Marcus Comstedt  if (!mkdir(hooksdir))
59cd492010-09-05Marcus 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);
52d8152010-09-05Marcus Comstedt  } else if (s->islnk) {
59cd492010-09-05Marcus Comstedt  /* Already setup ok, it seems */ } else {
5179182010-09-25Marcus Comstedt  write("Hook %s already exists, so won't overwrite it...\n", name);
59cd492010-09-05Marcus 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;
52d8152010-09-05Marcus Comstedt  if (old == "") {
59cd492010-09-05Marcus Comstedt  write("Installing filter operation %s\n", confname); run_git("config", confname, cmd); } else if(old == cmd) { /* Already has correct value */ } 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)
52d8152010-09-05Marcus Comstedt  if (filter[op])
59cd492010-09-05Marcus 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;
070acd2010-09-05Marcus Comstedt  return 0;
59cd492010-09-05Marcus 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]) {
52d8152010-09-05Marcus Comstedt  werror("Filter %s does not implement %s!\n", filter, fop); return 1;
59cd492010-09-05Marcus 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; } }