Subversion Repositories php_utils

Rev

Rev 81 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 81 Rev 82
Line 16... Line 16...
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
18
 * limitations under the License.
19
 */
19
 */
20
 
20
 
21
function git_get_latest_commit_message($git_dir) {
21
function git_get_latest_commit_id(string $git_dir): string {
22
        // First try an official git client
-
 
23
        $cmd = "git --git-dir=".escapeshellarg("$git_dir")." log -1 2>&1";
22
        // Note: The method "getTip()" of GLIP only implements "refs/heads/master" and "packed-refs" (but for packed-refs without "refs/remotes/origin/...")
24
        $ec = -1;
-
 
25
        $out = array();
-
 
26
        @exec($cmd, $out, $ec);
-
 
27
        $out = implode("\n",$out);
-
 
28
        if (($ec == 0) && ($out != '')) return $out;
-
 
29
 
23
 
30
        // If that failed, try to decode the binary files outselves
-
 
31
        $cont = @file_get_contents($git_dir.'/HEAD');
24
        $cont = @file_get_contents($git_dir . '/HEAD');
32
        if (preg_match('@ref: (.+)[\r\n]@', "$cont\n", $m) && file_exists($git_dir.'/'.$m[1])) {
25
        if (preg_match('@ref: (.+)[\r\n]@', "$cont\n", $m) && file_exists($git_dir . '/' . $m[1])) {
33
                // Example content of a .git folder file:
26
                // Example content of a .git folder file:
34
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63
27
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63
35
                // Not available
-
 
36
                $commit_object = trim(file_get_contents($git_dir.'/'.$m[1]));
28
                return trim(file_get_contents($git_dir . '/' . $m[1]));
-
 
29
        }
-
 
30
 
37
        } else if (file_exists($git_dir.'/refs/heads/master')) {
31
        if (file_exists($git_dir . '/refs/heads/master')) {
38
                // Missing at Plesk Git initial checkout, but available on update.
32
                // Missing at Plesk Git initial checkout, but available on update.
39
                $commit_object = trim(file_get_contents($git_dir.'/refs/heads/master'));
33
                return trim(file_get_contents($git_dir . '/refs/heads/master'));
-
 
34
        }
-
 
35
 
-
 
36
        if (file_exists($git_dir . '/packed-refs')) {
-
 
37
                // Example contents of the file:
-
 
38
                // # pack-refs with: peeled fully-peeled sorted
-
 
39
                // 5605bd539677494558470234266cb5885343e72b refs/remotes/origin/master
-
 
40
                // a3d910dd0cdca30827ae25b0f89045d8403b8843 refs/remotes/origin/patch-1
-
 
41
                $subpaths = ['refs/heads/master', 'refs/remotes/origin/master'];
-
 
42
                foreach ($subpaths as $subpath) {
-
 
43
                        $head = null;
-
 
44
                        $f = fopen($git_dir . '/packed-refs', 'rb');
-
 
45
                        flock($f, LOCK_SH);
-
 
46
                        while ($head === null && ($line = fgets($f)) !== false) {
-
 
47
                                if ($line[0] == '#')
-
 
48
                                        continue;
-
 
49
                                $parts = explode(' ', trim($line));
-
 
50
                                if (count($parts) == 2 && $parts[1] == $subpath)
-
 
51
                                        $head = $parts[0];
-
 
52
                        }
-
 
53
                        fclose($f);
-
 
54
                        if ($head !== null)
-
 
55
                                return $head;
-
 
56
                }
-
 
57
        }
-
 
58
 
40
        } else if (file_exists($git_dir.'/FETCH_HEAD')) {
59
        if (file_exists($git_dir . '/FETCH_HEAD')) {
41
                // Example content of a Plesk Git folder (fresh):
60
                // Example content of a Plesk Git folder (fresh):
42
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   branch 'master' of https://github.com/danielmarschall/oidplus
61
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   branch 'master' of https://github.com/danielmarschall/oidplus
43
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   remote-tracking branch 'origin/trunk' of https://github.com/danielmarschall/oidplus
62
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   remote-tracking branch 'origin/trunk' of https://github.com/danielmarschall/oidplus
44
                $cont = file_get_contents($git_dir.'/FETCH_HEAD');
63
                $cont = file_get_contents($git_dir . '/FETCH_HEAD');
45
                $commit_object = substr(trim($cont),0,40);
64
                return substr(trim($cont), 0, 40);
46
        } else {
-
 
47
                throw new Exception("Cannot detect last commit object");
-
 
48
        }
65
        }
49
 
66
 
-
 
67
        throw new Exception("Cannot detect latest Commit ID");
-
 
68
}
-
 
69
 
-
 
70
function git_get_latest_commit_message(string $git_dir): string {
-
 
71
        // First try an official git client
-
 
72
        $cmd = "git --git-dir=" . escapeshellarg("$git_dir") . " log -1 2>&1";
-
 
73
        $ec = -1;
-
 
74
        $out = array();
-
 
75
        @exec($cmd, $out, $ec);
50
        $objects_dir = $git_dir . '/objects';
76
        $out = implode("\n", $out);
-
 
77
        if (($ec == 0) && ($out != '')) return $out;
51
 
78
 
-
 
79
        // If that failed, try to decode the binary files ourselves
-
 
80
        $commit_object = git_get_latest_commit_id($git_dir);
-
 
81
        $objects_dir = $git_dir . '/objects';
52
 
82
 
53
        // Sometimes, objects are uncompressed, sometimes compressed in a pack file
83
        // Sometimes, objects are uncompressed, sometimes compressed in a pack file
54
        // Plesk initial checkout is compressed, but pulls via web interface
84
        // Plesk initial checkout is compressed, but pulls via web interface
55
        // save uncompressed files
85
        // save uncompressed files
56
 
86
 
57
        $uncompressed_file = $objects_dir . '/' . substr($commit_object,0,2) . '/' . substr($commit_object,2);
-
 
58
        if (file_exists($uncompressed_file)) {
87
        if (class_exists('ViaThinkSoft\Glip\Git')) {
59
                // Read compressed data
88
                // https://github.com/danielmarschall/glip
60
                $compressed = file_get_contents($uncompressed_file);
89
                // composer require danielmarschall/glip
61
 
-
 
62
                // Uncompress
-
 
63
                $uncompressed = @gzuncompress($compressed);
90
                $git = new ViaThinkSoft\Glip\Git($git_dir);
64
                if ($uncompressed === false) throw new Exception("Decompression failed");
-
 
65
 
-
 
66
                // The format is "commit <nnn>\0<Message>" where <nnn> is only a 3 digit number?!
-
 
67
                $ary = explode(chr(0), $uncompressed);
91
                $obj = $git->getObject(hex2bin($commit_object));
68
                $uncompressed = array_pop($ary);
-
 
69
 
-
 
70
                return $uncompressed;
92
                return $obj->detail;
71
        } else {
93
        } else {
72
                $pack_files = @glob($objects_dir.'/pack/pack-*.pack');
94
                // Own implementation (the compressed read cannot handle delta objects yet)
-
 
95
 
-
 
96
                $uncompressed_file = $objects_dir . '/' . substr($commit_object, 0, 2) . '/' . substr($commit_object, 2);
73
                $last_exception = 'No pack files found';
97
                if (file_exists($uncompressed_file)) {
74
                if ($pack_files) foreach ($pack_files as $basename) {
98
                        // Read compressed data
75
                        $basename = substr(basename($basename),0,strlen(basename($basename))-5);
99
                        $compressed = file_get_contents($uncompressed_file);
-
 
100
 
76
                        try {
101
                        // Uncompress
77
                                if (class_exists('ViaThinkSoft\Glip\Git')) {
102
                        $uncompressed = @gzuncompress($compressed);
78
                                        // https://github.com/danielmarschall/glip
103
                        if ($uncompressed === false) throw new Exception("Decompression failed");
-
 
104
 
79
                                        // composer require danielmarschall/glip
105
                        // The format is "<type> <size>\0<Message>"
80
                                        $git = new ViaThinkSoft\Glip\Git($git_dir);
106
                        list($hdr, $object_data) = explode("\0", $uncompressed, 2);
81
                                        $obj = $git->getObject(hex2bin($commit_object));
107
                        // sscanf($hdr, "%s %d", $type, $object_size);
82
                                        return $obj->detail;
108
                        return $object_data;
83
                                } else {
109
                } else {
84
                                        // Own implementation (cannot read delta objects yet)
110
                        $pack_files = @glob($objects_dir . '/pack/pack-*.pack');
-
 
111
                        if ($pack_files) {
-
 
112
                                foreach ($pack_files as $basename) {
-
 
113
                                        $basename = substr(basename($basename), 0, strlen(basename($basename)) - 5);
85
                                        return git_read_object($commit_object,
114
                                        return git_read_object($commit_object,
86
                                                $objects_dir.'/pack/'.$basename.'.idx',
115
                                                $objects_dir . '/pack/' . $basename . '.idx',
87
                                                $objects_dir.'/pack/'.$basename.'.pack'
116
                                                $objects_dir . '/pack/' . $basename . '.pack'
88
                                        );
117
                                        );
89
                                }
118
                                }
90
                        } catch (Exception $e) {
-
 
91
                                $last_exception = $e;
-
 
92
                        }
119
                        }
-
 
120
                        throw new Exception("No pack files found");
93
                }
121
                }
94
                throw new Exception($last_exception);
-
 
95
        }
122
        }
96
}
123
}
97
 
124
 
98
function git_read_object($object_wanted, $idx_file, $pack_file, $debug=false) {
125
function git_read_object(string $object_wanted, string $idx_file, string $pack_file, bool $debug = false): string {
99
        // More info about the IDX and PACK format: https://git-scm.com/docs/pack-format
126
        // More info about the IDX and PACK format: https://git-scm.com/docs/pack-format
100
 
127
 
101
        // Do some checks
128
        // Do some checks
102
        if (!preg_match('/^[0-9a-fA-F]{40}$/', $object_wanted, $m)) throw new Exception("Is not a valid object: $object_wanted");
129
        if (!preg_match('/^[0-9a-fA-F]{40}$/', $object_wanted, $m)) throw new Exception("Is not a valid object: $object_wanted");
103
        if (!file_exists($idx_file)) throw new Exception("Idx file $idx_file not found");
130
        if (!file_exists($idx_file)) throw new Exception("Idx file $idx_file not found");
Line 121... Line 148...
121
        if ($debug) echo "Index file version = $version\n";
148
        if ($debug) echo "Index file version = $version\n";
122
 
149
 
123
        // Read fanout table
150
        // Read fanout table
124
        fseek($fp, $fanout_offset);
151
        fseek($fp, $fanout_offset);
125
        $fanout_ary[0] = 0;
152
        $fanout_ary[0] = 0;
126
        $fanout_ary = unpack('N*', fread($fp, 4*256));
153
        $fanout_ary = unpack('N*', fread($fp, 4 * 256));
127
        $num_objects = $fanout_ary[256];
154
        $num_objects = $fanout_ary[256];
128
 
155
 
129
        // Find out approximate object number (from fanout table)
156
        // Find out approximate object number (from fanout table)
130
        $fanout_index = hexdec(substr($object_wanted,0,2));
157
        $fanout_index = hexdec(substr($object_wanted, 0, 2));
131
        if ($debug) echo "Fanout index = ".($fanout_index-1)."\n";
158
        if ($debug) echo "Fanout index = " . ($fanout_index - 1) . "\n";
132
        $object_no = $fanout_ary[$fanout_index]; // approximate
159
        $object_no = $fanout_ary[$fanout_index]; // approximate
133
        if ($debug) echo "Object no approx $object_no\n";
160
        if ($debug) echo "Object no approx $object_no\n";
134
 
161
 
135
        // Find the exact object number
162
        // Find the exact object number
136
        fseek($fp, $fanout_offset + 4*256 + 20*$object_no);
163
        fseek($fp, $fanout_offset + 4 * 256 + 20 * $object_no);
137
        $object_no--;
164
        $object_no--;
138
        $pack_offset = -1; // avoid that phpstan complains
165
        $pack_offset = -1; // avoid that phpstan complains
139
        do {
166
        do {
140
                $object_no++;
167
                $object_no++;
141
                if ($version == 1) {
168
                if ($version == 1) {
142
                        $pack_offset = fread($fp, 4);
169
                        $pack_offset = fread($fp, 4);
143
                }
170
                }
144
                $binary = fread($fp, 20);
171
                $binary = fread($fp, 20);
145
                if (substr(bin2hex($binary),0,2) != substr(strtolower($object_wanted),0,2)) {
172
                if (substr(bin2hex($binary), 0, 2) != substr(strtolower($object_wanted), 0, 2)) {
146
                        throw new Exception("Object $object_wanted not found");
173
                        throw new Exception("Object $object_wanted not found");
147
                }
174
                }
148
        } while (bin2hex($binary) != strtolower($object_wanted));
175
        } while (bin2hex($binary) != strtolower($object_wanted));
149
        if ($debug) echo "Exact object no = $object_no\n";
176
        if ($debug) echo "Exact object no = $object_no\n";
150
 
177
 
151
        if ($version == 2) {
178
        if ($version == 2) {
152
                // Get CRC32
179
                // Get CRC32
153
                fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$object_no);
180
                fseek($fp, $fanout_offset + 4 * 256 + 20 * $num_objects + 4 * $object_no);
154
                $crc32 = unpack('H8', fread($fp,4))[1];
181
                $crc32 = unpack('H8', fread($fp, 4))[1];
155
                if ($debug) echo "CRC32 = ".$crc32."\n";
182
                if ($debug) echo "CRC32 = " . $crc32 . "\n";
156
 
183
 
157
                // Get offset (32 bit)
184
                // Get offset (32 bit)
158
                fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$num_objects + 4*$object_no);
185
                fseek($fp, $fanout_offset + 4 * 256 + 20 * $num_objects + 4 * $num_objects + 4 * $object_no);
159
                $offset_info = unpack('N', fread($fp,4))[1];
186
                $offset_info = unpack('N', fread($fp, 4))[1];
160
                if ($offset_info >= 0x80000000) {
187
                if ($offset_info >= 0x80000000) {
161
                        // MSB set, so the offset is 64 bit
188
                        // MSB set, so the offset is 64 bit
162
                        if ($debug) echo "64 bit pack offset\n";
189
                        if ($debug) echo "64 bit pack offset\n";
163
                        $offset_info &= 0x7FFFFFFF;
190
                        $offset_info &= 0x7FFFFFFF;
164
                        fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$num_objects + 4*$num_objects + 8*$offset_info);
191
                        fseek($fp, $fanout_offset + 4 * 256 + 20 * $num_objects + 4 * $num_objects + 4 * $num_objects + 8 * $offset_info);
165
                        $pack_offset = unpack('J', fread($fp,8))[1];
192
                        $pack_offset = unpack('J', fread($fp, 8))[1];
166
                } else {
193
                } else {
167
                        // MSB is not set, so the offset is 32 bit
194
                        // MSB is not set, so the offset is 32 bit
168
                        if ($debug) echo "32 bit pack offset\n";
195
                        if ($debug) echo "32 bit pack offset\n";
169
                        $offset_info &= 0x7FFFFFFF;
196
                        $offset_info &= 0x7FFFFFFF;
170
                        $pack_offset = $offset_info;
197
                        $pack_offset = $offset_info;
171
                }
198
                }
172
        }
199
        }
173
 
200
 
174
        if ($debug) echo "Pack file offset = ".sprintf('0x%x',$pack_offset)."\n";
201
        if ($debug) echo "Pack file offset = " . sprintf('0x%x', $pack_offset) . "\n";
175
 
202
 
176
        // Close index file
203
        // Close index file
177
        fclose($fp);
204
        fclose($fp);
178
 
205
 
179
        // Open pack file
206
        // Open pack file
180
        $fp = fopen($pack_file, 'rb');
207
        $fp = fopen($pack_file, 'rb');
181
        if (!$fp) throw new Exception("Cannot open pack file $pack_file");
208
        if (!$fp) throw new Exception("Cannot open pack file $pack_file");
182
 
209
 
183
        // Read type and first part of the size
210
        // Read type and first part of the size
184
        fseek($fp, $pack_offset);
211
        fseek($fp, $pack_offset);
185
        $size_info = unpack('C', fread($fp,1))[1];
212
        $size_info = unpack('C', fread($fp, 1))[1];
186
 
213
 
187
        // Detect type
214
        // Detect type
188
        $type = ($size_info & 0x70) >> 4; /*0b01110000*/
215
        $type = ($size_info & 0x70) >> 4; /*0b01110000*/
189
        switch ($type) {
216
        switch ($type) {
190
                case 1:
217
                case 1:
Line 209... Line 236...
209
                        if ($debug) echo "Type = Invalid ($type)\n";
236
                        if ($debug) echo "Type = Invalid ($type)\n";
210
                        break;
237
                        break;
211
        }
238
        }
212
 
239
 
213
        // Find out the expected unpacked size
240
        // Find out the expected unpacked size
214
        $size = $size_info & 0xF /*0x00001111*/;
241
        $size = $size_info & 0xF /*0x00001111*/
-
 
242
        ;
215
        $shift_info = 4;
243
        $shift_info = 4;
216
        while ($size_info >= 0x80) {
244
        while ($size_info >= 0x80) {
217
                $size_info = unpack('C', fread($fp,1))[1];
245
                $size_info = unpack('C', fread($fp, 1))[1];
218
                $size = (($size_info & 0x7F) << $shift_info) + $size;
246
                $size = (($size_info & 0x7F) << $shift_info) + $size;
219
                $shift_info += 7;
247
                $shift_info += 7;
220
        }
248
        }
221
        if ($debug) echo "Expected unpacked size = $size\n";
249
        if ($debug) echo "Expected unpacked size = $size\n";
222
 
250
 
Line 227... Line 255...
227
 
255
 
228
                // Offset encoding
256
                // Offset encoding
229
                $offset = 0;
257
                $offset = 0;
230
                $shift_info = 0;
258
                $shift_info = 0;
231
                do {
259
                do {
232
                        $offset_info = unpack('C', fread($fp,1))[1];
260
                        $offset_info = unpack('C', fread($fp, 1))[1];
233
                        $offset = (($offset_info & 0x7F) << $shift_info) + $offset;
261
                        $offset = (($offset_info & 0x7F) << $shift_info) + $offset;
234
                        $shift_info += 7;
262
                        $shift_info += 7;
235
                } while ($offset_info >= 0x80);
263
                } while ($offset_info >= 0x80);
236
 
264
 
237
                if ($debug) echo "Delta negative offset: $offset\n";
265
                if ($debug) echo "Delta negative offset: $offset\n";
238
                throw new Exception("OBJ_OFS_DELTA is currently not implemented"); // TODO! Implement OBJ_OFS_DELTA!
266
                throw new Exception("OBJ_OFS_DELTA is currently not implemented"); // TODO! Implement OBJ_OFS_DELTA!
239
        }
267
        }
240
        if ($type == 7/*OBJ_REF_DELTA*/) {
268
        if ($type == 7/*OBJ_REF_DELTA*/) {
241
                // "base object name if OBJ_REF_DELTA"
269
                // "base object name if OBJ_REF_DELTA"
242
                $delta_info = bin2hex(fread($fp,20));
270
                $delta_info = bin2hex(fread($fp, 20));
243
                if ($debug) echo "Delta base object name: $delta_info\n";
271
                if ($debug) echo "Delta base object name: $delta_info\n";
244
                throw new Exception("OBJ_REF_DELTA is currently not implemented"); // TODO! Implement OBJ_REF_DELTA!
272
                throw new Exception("OBJ_REF_DELTA is currently not implemented"); // TODO! Implement OBJ_REF_DELTA!
245
        }
273
        }
246
 
274
 
247
        // Read and uncompress the compressed data
275
        // Read and uncompress the compressed data
248
        $compressed = '';
276
        $compressed = '';
249
        $uncompressed = false;
277
        $uncompressed = false;
250
        for ($compressed_size=1; $compressed_size<=32768*$size; $compressed_size++) {
278
        for ($compressed_size = 1; $compressed_size <= 32768 * $size; $compressed_size++) {
251
                // Since we don't know the compressed size, we need to do trial and error
279
                // Since we don't know the compressed size, we need to do trial and error
252
                // TODO: this is a super stupid algorithm! Is there a better way???
280
                // TODO: this is a super stupid algorithm! Is there a better way???
253
                $compressed .= fread($fp,1);
281
                $compressed .= fread($fp, 1);
254
                $uncompressed = @gzuncompress($compressed);
282
                $uncompressed = @gzuncompress($compressed);
255
                if (strlen($uncompressed) === $size) {
283
                if (strlen($uncompressed) === $size) {
256
                        if ($debug) echo "Detected compressed size = $compressed_size\n";
284
                        if ($debug) echo "Detected compressed size = $compressed_size\n";
257
                        break;
285
                        break;
258
                }
286
                }