448982baa2da6c54252731382fd746cc61d832e9
[phorkie.git] / src / phorkie / Repository.php
1 <?php
2 namespace phorkie;
3
4
5 class Repository
6 {
7     /**
8      * Repository ID (number in repositories directory)
9      *
10      * @var integer
11      */
12     public $id;
13
14     /**
15      * Full path to the .git repository
16      *
17      * @var string
18      */
19     public $gitDir;
20
21     /**
22      * Full path to the work tree directory
23      *
24      * @var string
25      */
26     public $workDir;
27
28     /**
29      * Revision of the repository that shall be shown
30      *
31      * @var string
32      */
33     public $hash;
34
35
36
37     /**
38      * Load Repository data from GET-Request
39      *
40      * @return void
41      *
42      * @throws Exception When something is wrong
43      */
44     public function loadFromRequest()
45     {
46         if (!isset($_GET['id'])) {
47             throw new Exception_Input('Paste ID missing');
48         }
49         if (!is_numeric($_GET['id'])) {
50             throw new Exception_Input('Paste ID not numeric');
51         }
52         if (isset($_GET['rev'])) {
53             $this->hash = $_GET['rev'];
54         }
55
56         $this->id = (int)$_GET['id'];
57         $this->loadDirs();
58         $this->loadHash();
59     }
60
61     protected function loadDirs()
62     {
63         $gitDir = $GLOBALS['phorkie']['cfg']['gitdir'] . '/' . $this->id . '.git';
64         if (!is_dir($gitDir)) {
65             throw new Exception_NotFound(
66                 sprintf('Paste %d .git dir not found', $this->id)
67             );
68         }
69         $this->gitDir = $gitDir;
70
71         $workDir = $GLOBALS['phorkie']['cfg']['workdir'] . '/' . $this->id;
72         if (!is_dir($workDir)) {
73             throw new Exception_NotFound(
74                 sprintf('Paste %d work dir not found', $this->id)
75             );
76         }
77         $this->workDir = $workDir;
78     }
79
80     public function loadHash()
81     {
82         return;
83         if ($this->hash !== null) {
84             return;
85         }
86
87         $output = $this->getVc()->getCommand('log')
88             ->setOption('pretty', 'format:%H')
89             ->setOption('max-count', 1)
90             ->execute();
91         $output = trim($output);
92         if (strlen($output) !== 40) {
93             throw new Exception(
94                 'Loading commit hash failed: ' . $output
95             );
96         }
97         $this->hash = $output;
98     }
99
100     public function loadById($id)
101     {
102         if (!is_numeric($id)) {
103             throw new Exception_Input('Paste ID not numeric');
104         }
105         $this->id = (int)$id;
106         $this->loadDirs();
107         $this->loadHash();
108     }
109
110     public function getVc()
111     {
112         return new \VersionControl_Git($this->gitDir);
113     }
114
115     /**
116      * Loads the list of files in this repository
117      *
118      * @return File[] Array of files
119      */
120     public function getFiles()
121     {
122         $files = $this->getFilePaths();
123         $arFiles = array();
124         foreach ($files as $name) {
125             $arFiles[] = new File($name, $this);
126         }
127         return $arFiles;
128     }
129
130     protected function getFilePaths()
131     {
132         if ($this->hash === null) {
133             $hash = 'HEAD';
134         } else {
135             $hash = $this->hash;
136         }
137         $output = $this->getVc()->getCommand('ls-tree')
138             ->setOption('r')
139             ->setOption('name-only')
140             ->addArgument($hash)
141             ->execute();
142         return explode("\n", trim($output));
143     }
144
145     public function getFileByName($name, $bHasToExist = true)
146     {
147         $name = Tools::sanitizeFilename($name);
148         if ($name == '') {
149             throw new Exception_Input('Empty file name given');
150         }
151
152         if ($bHasToExist) {
153             $files = $this->getFilePaths();
154             if (array_search($name, $files) === false) {
155                 throw new Exception_Input('File does not exist');
156             }
157         }
158         return new File($name, $this);
159     }
160
161     public function hasFile($name)
162     {
163         try {
164             $this->getFileByName($name);
165         } catch (Exception $e) {
166             return false;
167         }
168         return true;
169     }
170
171     /**
172      * Permanently deletes the paste repository without any way to get
173      * it back.
174      *
175      * @return boolean True if all went well, false if not
176      */
177     public function delete()
178     {
179         $db = new Database();
180         $db->getIndexer()->deleteRepo($this);
181
182         return Tools::recursiveDelete($this->workDir)
183             && Tools::recursiveDelete($this->gitDir);
184     }
185
186     public function getTitle()
187     {
188         $desc = $this->getDescription();
189         if (trim($desc) != '') {
190             return $desc;
191         }
192
193         return 'paste #' . $this->id;
194     }
195
196     public function getDescription()
197     {
198         if (!is_readable($this->gitDir . '/description')) {
199             return null;
200         }
201         return file_get_contents($this->gitDir . '/description');
202     }
203
204     public function setDescription($description)
205     {
206         file_put_contents($this->gitDir . '/description', $description);
207     }
208
209     /**
210      * Get a link to the repository
211      *
212      * @param string $type Link type. Supported are:
213      *                     - "edit"
214      *                     - "delete"
215      *                     - "delete-confirm"
216      *                     - "display"
217      *                     - "fork"
218      *                     - "revision"
219      * @param string $option
220      *
221      * @return string
222      */
223     public function getLink($type, $option = null)
224     {
225         if ($type == 'edit') {
226             return '/' . $this->id . '/edit';
227         } else if ($type == 'display') {
228             return '/' . $this->id;
229         } else if ($type == 'fork') {
230             return '/' . $this->id . '/fork';
231         } else if ($type == 'delete') {
232             return '/' . $this->id . '/delete';
233         } else if ($type == 'delete-confirm') {
234             return '/' . $this->id . '/delete/confirm';
235         } else if ($type == 'revision') {
236             return '/' . $this->id . '/rev/' . $option;
237         }
238         throw new Exception('Unknown link type');
239     }
240
241     public function getCloneURL($public = true)
242     {
243         $var = $public ? 'public' : 'private';
244         if (isset($GLOBALS['phorkie']['cfg']['git'][$var])) {
245             return $GLOBALS['phorkie']['cfg']['git'][$var] . $this->id . '.git';
246         }
247         return null;
248     }
249
250     /**
251      * Returns the history of the repository.
252      * We don't use VersionControl_Git's rev list fetcher since it does not
253      * give us separate email addresses and names, and it does not give us
254      * the amount of changed (added/deleted) lines.
255      *
256      * @return array Array of history objects
257      */
258     public function getHistory()
259     {
260         $output = $this->getVc()->getCommand('log')
261             ->setOption('pretty', 'format:commit %H%n%at%n%an%n%ae')
262             ->setOption('max-count', 10)
263             ->setOption('shortstat')
264             ->execute();
265
266         $arCommits = array();
267         $arOutput = explode("\n", $output);
268         $lines = count($arOutput);
269         $current = 0;
270         while ($current < $lines) {
271             $commit = new Repository_Commit();
272             list($name,$commit->hash) = explode(' ', $arOutput[$current]);
273             if ($name !== 'commit') {
274                 throw new Exception(
275                     'Git log output format not as expected: ' . $arOutput[$current]
276                 );
277             }
278             $commit->committerTime  = $arOutput[$current + 1];
279             $commit->committerName  = $arOutput[$current + 2];
280             $commit->committerEmail = $arOutput[$current + 3];
281
282             $arLineParts = explode(' ', trim($arOutput[$current + 4]));
283             $commit->filesChanged = $arLineParts[0];
284             $commit->linesAdded   = $arLineParts[3];
285             if (isset($arLineParts[5])) {
286                 $commit->linesDeleted = $arLineParts[5];
287             }
288
289             $current += 6;
290
291             $arCommits[] = $commit;
292         }
293
294         return $arCommits;
295     }
296 }
297
298 ?>