Branch: Tag:

2010-09-25

2010-09-25 20:01:26 by Marcus Comstedt <marcus@mc.pp.se>

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... */