Subversion Repositories php_utils

Rev

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