Subversion Repositories php_utils

Rev

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