Subversion Repositories php_utils

Rev

Rev 14 | Rev 16 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  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.         $last_exception = 'No pack files found';
  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
  186.         $uncompressed = gzuncompress($compressed);
  187.         if ($debug) echo "$uncompressed\n";
  188.  
  189.         // Close pack file
  190.         fclose($fp);
  191.  
  192.         // Check CRC32
  193.         // TODO: Does not fit, not crc32 nor crc32b...
  194.         // if ($debug) echo "CRC32 found = 0x".hash('crc32',$compressed)."\n";
  195.  
  196.         return $uncompressed;
  197. }
  198.