Subversion Repositories php_utils

Rev

Rev 18 | Rev 20 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
14 daniel-mar 1
<?php
2
 
3
/*
4
 * PHP git functions
5
 * Copyright 2021 Daniel Marschall, ViaThinkSoft
19 daniel-mar 6
 * Revision 2021-12-09
14 daniel-mar 7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
function git_get_latest_commit_message($git_dir) {
19 daniel-mar 22
        $cont = @file_get_contents($git_dir.'/HEAD');
23
        if (preg_match('@ref: (.+)[\r\n]@', "$cont\n", $m) && file_exists($git_dir.'/'.$m[1])) {
18 daniel-mar 24
                // Example content of a .git folder file:
25
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63
19 daniel-mar 26
                // Not available
18 daniel-mar 27
                $commit_object = trim(file_get_contents($git_dir.'/'.$m[1]));
19 daniel-mar 28
        } else if (file_exists($git_dir.'/refs/heads/master')) {
29
                // Missing at Plesk Git initial checkout, but available on update.
30
                $commit_object = trim(file_get_contents($git_dir.'/refs/heads/master'));
18 daniel-mar 31
        } else if (file_exists($git_dir.'/FETCH_HEAD')) {
19 daniel-mar 32
                // Example content of a Plesk Git folder (fresh):
18 daniel-mar 33
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   branch 'master' of https://github.com/danielmarschall/oidplus
34
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   remote-tracking branch 'origin/trunk' of https://github.com/danielmarschall/oidplus
35
                $cont = file_get_contents($git_dir.'/FETCH_HEAD');
36
                $commit_object = substr(trim($cont),0,40);
37
        } else {
38
                throw new Exception("Cannot detect last commit object");
39
        }
14 daniel-mar 40
 
41
        $objects_dir = $git_dir . '/objects';
42
 
19 daniel-mar 43
 
44
        // Sometimes, objects are uncompressed, sometimes compressed in a pack file
45
        // Plesk initial checkout is compressed, but pulls via web interface
46
        // save uncompressed files
47
 
48
        $uncompressed_file = $objects_dir . '/' . substr($commit_object,0,2) . '/' . substr($commit_object,2);
49
        if (file_exists($uncompressed_file)) {
50
                // Read compressed data
51
                $compressed = file_get_contents($uncompressed_file);
52
 
53
                // Uncompress
54
                $uncompressed = @gzuncompress($compressed);
55
                if ($uncompressed === false) throw new Exception("Decompression failed");
56
 
57
                // The format is "commit <nnn>\0<Message>"
58
                $ary = explode(chr(0), $uncompressed);
59
                $uncompressed = array_pop($ary);
60
 
61
                return $uncompressed;
62
        } else {
63
                $pack_files = glob($objects_dir.'/pack/pack-*.pack');
64
                $last_exception = 'No pack files found';
65
                foreach ($pack_files as $basename) {
66
                        $basename = substr(basename($basename),0,strlen(basename($basename))-5);
67
                        try {
68
                                return git_read_object($commit_object,
69
                                        $objects_dir.'/pack/'.$basename.'.idx',
70
                                        $objects_dir.'/pack/'.$basename.'.pack',
71
                                        false
72
                                );
73
                        } catch (Exception $e) {
74
                                $last_exception = $e;
75
                        }
14 daniel-mar 76
                }
19 daniel-mar 77
                throw new Exception($last_exception);
14 daniel-mar 78
        }
79
}
80
 
81
function git_read_object($object_wanted, $idx_file, $pack_file, $debug=false) {
82
        // More info about the IDX and PACK format: https://git-scm.com/docs/pack-format
83
 
84
        // Do some checks
85
        if (!preg_match('/^[0-9a-fA-F]{40}$/', $object_wanted, $m)) throw new Exception("Is not a valid object: $object_wanted");
86
        if (!file_exists($idx_file)) throw new Exception("Idx file $idx_file not found");
87
        if (!file_exists($pack_file)) throw new Exception("Pack file $pack_file not found");
88
 
89
        // Open index file
90
        $fp = fopen($idx_file, 'rb');
91
        if (!$fp) throw new Exception("Cannot open index file $idx_file");
92
 
93
        // Read version
94
        fseek($fp, 0);
95
        $unpacked = unpack('N', fread($fp, 4)); // vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Big-Endian)
96
        if ($unpacked[1] === 0xFF744F63) {
97
                $version = unpack('N', fread($fp, 4))[1]; // vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Big-Endian)
98
                $fanout_offset = 8;
99
                if ($version != 2) throw new Exception("Version $version unknown");
100
        } else {
101
                $version = 1;
102
                $fanout_offset = 0;
103
        }
104
        if ($debug) echo "Index file version = $version\n";
105
 
106
        // Read fanout table
107
        fseek($fp, $fanout_offset);
108
        $fanout_ary[0] = 0;
109
        $fanout_ary = unpack('N*', fread($fp, 4*256));
110
        $num_objects = $fanout_ary[256];
111
 
112
        // Find out approximate object number (from fanout table)
113
        $fanout_index = hexdec(substr($object_wanted,0,2));
114
        if ($debug) echo "Fanout index = ".($fanout_index-1)."\n";
115
        $object_no = $fanout_ary[$fanout_index]; // approximate
116
        if ($debug) echo "Object no approx $object_no\n";
117
 
118
        // Find the exact object number
119
        fseek($fp, $fanout_offset + 4*256 + 20*$object_no);
120
        $object_no--;
121
        do {
122
                $object_no++;
123
                if ($version == 1) {
124
                        $pack_offset = fread($fp, 4);
125
                }
126
                $binary = fread($fp, 20);
127
                if (substr(bin2hex($binary),0,2) != substr(strtolower($object_wanted),0,2)) {
128
                        throw new Exception("Object $object_wanted not found");
129
                }
130
        } while (bin2hex($binary) != strtolower($object_wanted));
131
        if ($debug) echo "Exact object no = $object_no\n";
132
 
133
        if ($version == 2) {
134
                // Get CRC32
135
                fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$object_no);
136
                $crc32 = unpack('N', fread($fp,4))[1];
137
                if ($debug) echo "CRC32 = ".sprintf('0x%08x',$crc32)."\n";
138
 
139
                // Get offset (32 bit)
140
                fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$num_objects + 4*$object_no);
141
                $offset_info = unpack('N', fread($fp,4))[1];
142
                if ($offset_info >= 0x80000000) {
143
                        // MSB set, so the offset is 64 bit
144
                        if ($debug) echo "64 bit pack offset\n";
145
                        $offset_info &= 0x7FFFFFFF;
146
                        fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$num_objects + 4*$num_objects + 8*$offset_info);
147
                        $pack_offset = unpack('J', fread($fp,8))[1];
148
                } else {
149
                        // MSB is not set, so the offset is 32 bit
150
                        if ($debug) echo "32 bit pack offset\n";
151
                        $offset_info &= 0x7FFFFFFF;
152
                        $pack_offset = $offset_info;
153
                }
154
        }
155
 
156
        if ($debug) echo "Pack file offset = ".sprintf('0x%x',$pack_offset)."\n";
157
 
158
        // Close index file
159
        fclose($fp);
160
 
161
        // Open pack file
162
        $fp = fopen($pack_file, 'rb');
163
        if (!$fp) throw new Exception("Cannot open pack file $pack_file");
164
 
165
        // Find out type
166
        fseek($fp, $pack_offset);
167
        $size_info = unpack('C', fread($fp,1))[1];
168
 
169
        $type = ($size_info & 0xE0) >> 5; /*0b11100000*/
170
        switch ($type) {
171
                case 1:
172
                        if ($debug) echo "Type = OBJ_COMMIT ($type)\n";
173
                        break;
174
                case 2:
175
                        if ($debug) echo "Type = OBJ_TREE ($type)\n";
176
                        break;
177
                case 3:
178
                        if ($debug) echo "Type = OBJ_BLOB ($type)\n";
179
                        break;
180
                case 4:
181
                        if ($debug) echo "Type = OBJ_TAG ($type)\n";
182
                        break;
183
                case 6:
184
                        if ($debug) echo "Type = OBJ_OFS_DELTA ($type)\n";
185
                        break;
186
                case 7:
187
                        if ($debug) echo "Type = OBJ_REF_DELTA ($type)\n";
188
                        break;
189
                default:
190
                        if ($debug) echo "Type = Invalid ($type)\n";
191
                        break;
192
        }
193
 
194
        // Find out size
195
        $size = $size_info & 0x1F /*0x00011111*/;
196
        $shift_info = 5;
197
        do {
198
                $size_info = unpack('C', fread($fp,1))[1];
199
                $size = (($size_info & 0x7F) << $shift_info) + $size;
200
                $shift_info += 8;
19 daniel-mar 201
        } while ($size_info >= 0x80);
14 daniel-mar 202
 
203
        if ($debug) echo "Packed size = ".sprintf('0x%x',$size)."\n";
204
 
205
        // Read delta base type
206
        if ($type == 6/*OBJ_OFS_DELTA*/) {
207
                // "a negative relative offset from the delta object's position in the pack
208
                // if this is an OBJ_OFS_DELTA object"
209
                $delta_info = unpack('C*', fread($fp,4))[1]; // TODO?!
210
                if ($debug) echo "Delta negative offset: $delta_info\n";
211
        }
212
        if ($type == 7/*OBJ_REF_DELTA*/) {
213
                // "base object name if OBJ_REF_DELTA"
214
                $delta_info = bin2hex(fread($fp,20))[1]; // TODO?!
215
                if ($debug) echo "Delta base object name: $delta_info\n";
216
        }
217
 
218
        // Read compressed data
219
        $compressed = fread($fp,$size);
220
 
221
        // Uncompress
16 daniel-mar 222
        $uncompressed = @gzuncompress($compressed);
223
        if ($uncompressed === false) throw new Exception("Decompression failed");
14 daniel-mar 224
        if ($debug) echo "$uncompressed\n";
225
 
226
        // Close pack file
227
        fclose($fp);
228
 
229
        // Check CRC32
19 daniel-mar 230
        // TODO: Does not fit; neither crc32, nor crc32b...
14 daniel-mar 231
        // if ($debug) echo "CRC32 found = 0x".hash('crc32',$compressed)."\n";
232
 
233
        return $uncompressed;
234
}