import
[web.mtrack] / bin / hg-commit-hook
1 #!/usr/bin/env php
2 <?php # vim:ts=2:sw=2:et:ft=php:
3 /* For licensing and copyright terms, see the file named LICENSE */
4 // called as:
5 // hg-commit-hook what [mtrackconfig]
6 // the cwd is the repo path
7
8 putenv("GATEWAY_INTERFACE=");
9
10 $action = $argv[1];
11 if (isset($argv[2])) {
12   putenv("MTRACK_CONFIG_FILE=" . $argv[2]);
13 }
14 include dirname(__FILE__) . '/../inc/common.php';
15 if (file_exists(MTrackConfig::get('core', 'vardir') . '/.initializing')) {
16   exit(0);
17 }
18
19 ini_set('display_errors', true);
20 $HG = MTrackConfig::get('tools', 'hg');
21 if (!strlen($HG)) {
22   $HG = $_ENV['HG'];
23 }
24 $HG_NODE = $_ENV['HG_NODE'];
25 if (!isset($_ENV['HG_PARENT1']) || !strlen($_ENV['HG_PARENT1'])) {
26   # figure out the parent
27   $p = stream_get_contents(run($HG, 'log', "-r$HG_NODE",
28          '--template', '{parents}'));
29   foreach (preg_split("/\s+/", $p) as $item) {
30     if (preg_match("/^(\d+):(\S+)$/", $item, $M)) {
31       if ($M[1] >= 0) {
32         $HG_PARENT1 = $M[2];
33         break;
34       }
35     }
36   }
37 } else {
38   $HG_PARENT1 = $_ENV['HG_PARENT1'];
39 }
40
41
42 class HgCommitHookBridge implements IMTrackCommitHookBridge2 {
43   var $repo;
44   function __construct($repo) {
45     $this->repo = $repo;
46   }
47
48   function getChanges() {
49     global $HG_NODE;
50     global $HG;
51     $cs = array();
52     $log = popen("$HG log -r$HG_NODE: --template '{node|short}\n{author|email}\n{date|hgdate}\n{desc|nonempty|tabindent}\n'", 'r');
53     $line = fgets($log);
54     do {
55       $c = new MTrackCommitHookChangeEvent;
56
57       $node = trim($line);
58       $c->hash = $node;
59       $c->rev = "[changeset:" . $this->repo->getBrowseRootName() . ",$node]";
60
61       $author = trim(fgets($log));
62       $c->changeby = mtrack_canon_username($author);
63
64       $date = fgets($log);
65       if (!preg_match("/^(\d+)\s+\d+$/", $date, $M)) {
66         throw new Exception("failed to parse date $date");
67       }
68       $c->ctime = MTrackDB::unixtime((int)$M[1]);
69
70       $msg = fgets($log);
71       do {
72         $line = fgets($log);
73         if ($line === false) {
74           break;
75         }
76         if (preg_match("/^[a-fA-F0-9]+$/", $line)) {
77           break;
78         }
79         $msg .= substr($line, 1);
80       } while (true);
81       $c->changelog = rtrim($msg);
82       $cs[] = $c;
83     } while ($line !== false);
84
85     return $cs;
86   }
87
88   function enumChangedOrModifiedFileNames() {
89     global $HG;
90     global $HG_NODE;
91
92     $files = array();
93     $fp = popen("$HG log -r$HG_NODE: --template '{files}\n'", 'r');
94     while (($line = fgets($fp)) !== false) {
95       foreach (preg_split("/\s+/", $line) as $path) {
96         if (strlen($path)) {
97           $files[] = $path;
98         }
99       }
100     }
101     return $files;
102   }
103
104   function getCommitMessage() {
105     global $HG;
106     global $HG_NODE;
107     $fp = popen("$HG log -r$HG_NODE: --template '{desc}\n\n'", 'r');
108     $log = stream_get_contents($fp);
109     $log = preg_replace('/\[(\d+)\]/',
110       "[changeset:" . $this->repo->getBrowseRootName() . ",\$1]", $log);
111     return $log;
112   }
113
114   function getFileStream($path) {
115     global $HG;
116     global $HG_NODE;
117     return popen("$HG cat $path", 'r');
118   }
119
120   function getChangesetDescriptor() {
121     global $HG_NODE;
122     global $HG;
123     $cs = array();
124     $nodes = popen("$HG log -r$HG_NODE: --template '{node|short}\n'", 'r');
125     while (($line = fgets($nodes)) !== false) {
126       $n = trim($line);
127       $cs[] = '[changeset:' . $this->repo->getBrowseRootName() . ",$n]";
128     }
129     return join(", ", $cs);
130   }
131 }
132
133 try {
134   $repo = MTrackRepo::loadByLocation(getcwd());
135   $bridge = new HgCommitHookBridge($repo);
136   /* for pushes, respect OS indication of who this is, unless we don't
137    * know; we'll use the info from the changeset in that case */
138   $author = 'anonymous';
139   if (strstr($action, 'group')) {
140     $author = MTrackAuth::whoami();
141   }
142   if ($author == 'anonymous') {
143     $author = trim(
144               shell_exec("$HG log -r$HG_NODE: --template '{author|email}'"));
145   }
146   $author = mtrack_canon_username($author);
147   MTrackAuth::su($author);
148   $checker = new MTrackCommitChecker($repo);
149   switch ($action) {
150     case 'pretxncommit':
151     case 'pretxnchangegroup':
152       $checker->preCommit($bridge);
153       break;
154     default:
155       $checker->postCommit($bridge);
156   }
157   exit(0);
158 } catch (Exception $e) {
159   /* Errors must render to STDERR, or they won't show up in the hg client */
160   fwrite(STDERR, "\n" . $e->getMessage() .
161     "\n\n" .
162     $e->getTraceAsString() .
163     "\n\n ** Commit failed [$action]\n");
164
165   exit(1);
166 }
167
168 function run()
169 {
170   $args = func_get_args();
171   $all_args = array();
172   foreach ($args as $a) {
173     if (is_array($a)) {
174       foreach ($a as $arg) {
175          $all_args[] = $arg;
176       }
177     } else {
178       $all_args[] = $a;
179     }
180   }
181
182   $cmd = '';
183
184   foreach ($all_args as $i => $arg) {
185     if ($i > 0) {
186       $cmd .= ' ';
187     }
188     $cmd .= escapeshellarg($arg);
189   }
190
191 //  echo $cmd, "\n";
192   return popen($cmd, 'r');
193 }