Subversion Repositories php_utils

Rev

Rev 16 | Rev 19 | 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
18 daniel-mar 6
 * Revision 2021-12-07
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) {
22
        $cont = file_get_contents($git_dir.'/HEAD');
23
        if (!preg_match('@ref: (.+)[\r\n]@', "$cont\n", $m)) throw new Exception("Cannot find HEAD ref");
18 daniel-mar 24
        if (file_exists($git_dir.'/'.$m[1])) {
25
                // Example content of a .git folder file:
26
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63
27
                $commit_object = trim(file_get_contents($git_dir.'/'.$m[1]));
28
        } else if (file_exists($git_dir.'/FETCH_HEAD')) {
29
                // Example content of a Plesk Git folder:
30
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   branch 'master' of https://github.com/danielmarschall/oidplus
31
                // 091a5fa6b157be035e88f5d24aa329ba44d20d63     not-for-merge   remote-tracking branch 'origin/trunk' of https://github.com/danielmarschall/oidplus
32
                $cont = file_get_contents($git_dir.'/FETCH_HEAD');
33
                $commit_object = substr(trim($cont),0,40);
34
        } else {
35
                throw new Exception("Cannot detect last commit object");
36
        }
14 daniel-mar 37
 
38
        $objects_dir = $git_dir . '/objects';
39
 
15 daniel-mar 40
        $pack_files = glob($objects_dir.'/pack/pack-*.pack');
41
        $last_exception = 'No pack files found';
14 daniel-mar 42
        foreach ($pack_files as $basename) {
43
                $basename = substr(basename($basename),0,strlen(basename($basename))-5);
44
                try {
45
                        return git_read_object($commit_object,
46
                                $objects_dir.'/pack/'.$basename.'.idx',
47
                                $objects_dir.'/pack/'.$basename.'.pack',
48
                                false
49
                        );
50
                } catch (Exception $e) {
51
                        $last_exception = $e;
52
                }
53
        }
54
        throw new Exception($last_exception);
55
}
56
 
57
function git_read_object($object_wanted, $idx_file, $pack_file, $debug=false) {
58
        // More info about the IDX and PACK format: https://git-scm.com/docs/pack-format
59
 
60
        // Do some checks
61
        if (!preg_match('/^[0-9a-fA-F]{40}$/', $object_wanted, $m)) throw new Exception("Is not a valid object: $object_wanted");
62
        if (!file_exists($idx_file)) throw new Exception("Idx file $idx_file not found");
63
        if (!file_exists($pack_file)) throw new Exception("Pack file $pack_file not found");
64
 
65
        // Open index file
66
        $fp = fopen($idx_file, 'rb');
67
        if (!$fp) throw new Exception("Cannot open index file $idx_file");
68
 
69
        // Read version
70
        fseek($fp, 0);
71
        $unpacked = unpack('N', fread($fp, 4)); // vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Big-Endian)
72
        if ($unpacked[1] === 0xFF744F63) {
73
                $version = unpack('N', fread($fp, 4))[1]; // vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Big-Endian)
74
                $fanout_offset = 8;
75
                if ($version != 2) throw new Exception("Version $version unknown");
76
        } else {
77
                $version = 1;
78
                $fanout_offset = 0;
79
        }
80
        if ($debug) echo "Index file version = $version\n";
81
 
82
        // Read fanout table
83
        fseek($fp, $fanout_offset);
84
        $fanout_ary[0] = 0;
85
        $fanout_ary = unpack('N*', fread($fp, 4*256));
86
        $num_objects = $fanout_ary[256];
87
 
88
        // Find out approximate object number (from fanout table)
89
        $fanout_index = hexdec(substr($object_wanted,0,2));
90
        if ($debug) echo "Fanout index = ".($fanout_index-1)."\n";
91
        $object_no = $fanout_ary[$fanout_index]; // approximate
92
        if ($debug) echo "Object no approx $object_no\n";
93
 
94
        // Find the exact object number
95
        fseek($fp, $fanout_offset + 4*256 + 20*$object_no);
96
        $object_no--;
97
        do {
98
                $object_no++;
99
                if ($version == 1) {
100
                        $pack_offset = fread($fp, 4);
101
                }
102
                $binary = fread($fp, 20);
103
                if (substr(bin2hex($binary),0,2) != substr(strtolower($object_wanted),0,2)) {
104
                        throw new Exception("Object $object_wanted not found");
105
                }
106
        } while (bin2hex($binary) != strtolower($object_wanted));
107
        if ($debug) echo "Exact object no = $object_no\n";
108
 
109
        if ($version == 2) {
110
                // Get CRC32
111
                fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$object_no);
112
                $crc32 = unpack('N', fread($fp,4))[1];
113
                if ($debug) echo "CRC32 = ".sprintf('0x%08x',$crc32)."\n";
114
 
115
                // Get offset (32 bit)
116
                fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$num_objects + 4*$object_no);
117
                $offset_info = unpack('N', fread($fp,4))[1];
118
                if ($offset_info >= 0x80000000) {
119
                        // MSB set, so the offset is 64 bit
120
                        if ($debug) echo "64 bit pack offset\n";
121
                        $offset_info &= 0x7FFFFFFF;
122
                        fseek($fp, $fanout_offset + 4*256 + 20*$num_objects + 4*$num_objects + 4*$num_objects + 8*$offset_info);
123
                        $pack_offset = unpack('J', fread($fp,8))[1];
124
                } else {
125
                        // MSB is not set, so the offset is 32 bit
126
                        if ($debug) echo "32 bit pack offset\n";
127
                        $offset_info &= 0x7FFFFFFF;
128
                        $pack_offset = $offset_info;
129
                }
130
        }
131
 
132
        if ($debug) echo "Pack file offset = ".sprintf('0x%x',$pack_offset)."\n";
133
 
134
        // Close index file
135
        fclose($fp);
136
 
137
        // Open pack file
138
        $fp = fopen($pack_file, 'rb');
139
        if (!$fp) throw new Exception("Cannot open pack file $pack_file");
140
 
141
        // Find out type
142
        fseek($fp, $pack_offset);
143
        $size_info = unpack('C', fread($fp,1))[1];
144
 
145
        $type = ($size_info & 0xE0) >> 5; /*0b11100000*/
146
        switch ($type) {
147
                case 1:
148
                        if ($debug) echo "Type = OBJ_COMMIT ($type)\n";
149
                        break;
150
                case 2:
151
                        if ($debug) echo "Type = OBJ_TREE ($type)\n";
152
                        break;
153
                case 3:
154
                        if ($debug) echo "Type = OBJ_BLOB ($type)\n";
155
                        break;
156
                case 4:
157
                        if ($debug) echo "Type = OBJ_TAG ($type)\n";
158
                        break;
159
                case 6:
160
                        if ($debug) echo "Type = OBJ_OFS_DELTA ($type)\n";
161
                        break;
162
                case 7:
163
                        if ($debug) echo "Type = OBJ_REF_DELTA ($type)\n";
164
                        break;
165
                default:
166
                        if ($debug) echo "Type = Invalid ($type)\n";
167
                        break;
168
        }
169
 
170
        // Find out size
171
        $size = $size_info & 0x1F /*0x00011111*/;
172
        $shift_info = 5;
173
        do {
174
                $size_info = unpack('C', fread($fp,1))[1];
175
                $size = (($size_info & 0x7F) << $shift_info) + $size;
176
                $shift_info += 8;
177
        } while ($offset_info >= 0x80000000);
178
 
179
        if ($debug) echo "Packed size = ".sprintf('0x%x',$size)."\n";
180
 
181
        // Read delta base type
182
        if ($type == 6/*OBJ_OFS_DELTA*/) {
183
                // "a negative relative offset from the delta object's position in the pack
184
                // if this is an OBJ_OFS_DELTA object"
185
                $delta_info = unpack('C*', fread($fp,4))[1]; // TODO?!
186
                if ($debug) echo "Delta negative offset: $delta_info\n";
187
        }
188
        if ($type == 7/*OBJ_REF_DELTA*/) {
189
                // "base object name if OBJ_REF_DELTA"
190
                $delta_info = bin2hex(fread($fp,20))[1]; // TODO?!
191
                if ($debug) echo "Delta base object name: $delta_info\n";
192
        }
193
 
194
        // Read compressed data
195
        $compressed = fread($fp,$size);
196
 
197
        // Uncompress
16 daniel-mar 198
        $uncompressed = @gzuncompress($compressed);
199
        if ($uncompressed === false) throw new Exception("Decompression failed");
14 daniel-mar 200
        if ($debug) echo "$uncompressed\n";
201
 
202
        // Close pack file
203
        fclose($fp);
204
 
205
        // Check CRC32
206
        // TODO: Does not fit, not crc32 nor crc32b...
207
        // if ($debug) echo "CRC32 found = 0x".hash('crc32',$compressed)."\n";
208
 
209
        return $uncompressed;
210
}