Subversion Repositories php_utils

Rev

Rev 16 | Rev 19 | 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-12-07
  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.         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.         }
  37.  
  38.         $objects_dir = $git_dir . '/objects';
  39.  
  40.         $pack_files = glob($objects_dir.'/pack/pack-*.pack');
  41.         $last_exception = 'No pack files found';
  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
  198.         $uncompressed = @gzuncompress($compressed);
  199.         if ($uncompressed === false) throw new Exception("Decompression failed");
  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. }
  211.