1
0

phppatcher.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. /**
  3. * PhpPatcher class
  4. * @author legolas558
  5. * @version 1.0
  6. *
  7. * improved, code cleanup and PHP5 done by jumpin.banana@gmail.com 2012
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
  11. *
  12. * You should have received a copy of the
  13. * COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
  14. * along with this program. If not, see http://www.sun.com/cddl/cddl.html
  15. *
  16. *
  17. * Facility to merge unified diff files
  18. * First use Merge() and then ApplyPatch() to commit changes
  19. * files will be created, updated and/or deleted
  20. *
  21. */
  22. define('_PHPP_INVALID_INPUT', 'Invalid input');
  23. define('_PHPP_UNEXPECTED_EOF', 'Unexpected end of file');
  24. define('_PHPP_UNEXPECTED_ADD_LINE', 'Unexpected add line at line %d');
  25. define('_PHPP_UNEXPECTED_REMOVE_LINE', 'Unexpected remove line at line %d');
  26. define('_PHPP_INVALID_DIFF', 'Invalid unified diff block');
  27. define('_PHPP_FAILED_VERIFY', 'Failed source verification of file %s at line %d');
  28. class PhpPatcher {
  29. var $root;
  30. var $msg;
  31. var $sources = array();
  32. var $destinations = array();
  33. var $removals = array();
  34. var $newline = "\n";
  35. public function __construct($root_path) {
  36. // if you specify a root path all paths will be intended as relative to it (and not written, too)
  37. $this->root = $root_path;
  38. }
  39. public function Merge($udiff) {
  40. $lines = $this->_linesplit($udiff);
  41. if (!isset($lines)) {
  42. $this->msg = _PHPP_INVALID_INPUT;
  43. return false;
  44. }
  45. unset($udiff);
  46. $line = current($lines);
  47. do {
  48. if (strlen($line)<5) {
  49. continue;
  50. }
  51. // start recognition when a new diff block is found
  52. if (substr($line, 0, 4)!='--- ') {
  53. continue;
  54. }
  55. $p = strpos($line, "\t", 4);
  56. if ($p===false) $p = strlen($line);
  57. $src = $this->root.substr($line, 4, $p-4);
  58. $line = next($lines);
  59. if (!isset($line)) {
  60. $this->msg = _PHPP_UNEXPECTED_EOF;
  61. return false;
  62. }
  63. if (substr($line, 0, 4)!='+++ ') {
  64. $this->msg = _PHPP_INVALID_DIFF;
  65. return false;
  66. }
  67. $p = strpos($line, "\t", 4);
  68. if ($p===false) $p = strlen($line);
  69. $dst = $this->root.substr($line, 4, $p-4);
  70. $line = next($lines);
  71. if (!isset($line)) {
  72. $this->msg = _PHPP_UNEXPECTED_EOF;
  73. return false;
  74. }
  75. $done=0;
  76. while (preg_match('/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A', $line, $m)) {
  77. if ($m[3]==='')
  78. $src_size = 1;
  79. else $src_size = (int)$m[3];
  80. if ($m[6]==='')
  81. $dst_size = 1;
  82. else $dst_size = (int)$m[6];
  83. if (!$this->_apply_diff($lines, $src, $dst,
  84. (int)$m[1], $src_size, (int)$m[4],
  85. $dst_size))
  86. return false;
  87. $done++;
  88. $line = next($lines);
  89. if ($line === FALSE)
  90. break 2;
  91. }
  92. if ($done==0) {
  93. $this->msg = _PHPP_INVALID_DIFF;
  94. return false;
  95. }
  96. } while (FALSE !== ($line = next($lines)));
  97. //NOTE: previously opened files are still cached
  98. return true;
  99. }
  100. function ClearCache() {
  101. $this->sources = array();
  102. $this->destinations = array();
  103. $this->removals = array();
  104. }
  105. function ApplyPatch() {
  106. if (empty($this->destinations))
  107. return 0;
  108. $done = 0;
  109. $files = array_keys($this->destinations);
  110. foreach($files as $file) {
  111. $f = @fopen($file, 'w');
  112. if ($f===null)
  113. continue;
  114. fwrite($f, implode($this->newline, $this->destinations[$file]));
  115. fclose($f);
  116. $done++;
  117. }
  118. foreach($this->removals as $file) {
  119. if (@unlink($file))
  120. $done++;
  121. if (isset($this->sources[$file]))
  122. unset($this->sources[$file]);
  123. }
  124. $this->destinations = array(); // clear the destinations cache
  125. $this->removals = array();
  126. return $done;
  127. }
  128. private function &_get_source($src) {
  129. if (isset($this->sources[$src]))
  130. return $this->sources[$src];
  131. if (!is_readable($src)) {
  132. return null;
  133. }
  134. $_data = file_get_contents($src);
  135. $this->sources[$src] = $this->_linesplit($_data);
  136. return $this->sources[$src];
  137. }
  138. private function &_get_destin($dst, $src) {
  139. if (isset($this->destinations[$dst]))
  140. return $this->destinations[$dst];
  141. $this->destinations[$dst] = $this->_get_source($src);
  142. return $this->destinations[$dst];
  143. }
  144. // separate CR or CRLF lines
  145. private function &_linesplit(&$data) {
  146. $lines = preg_split('/(\r\n)|(\r)|(\n)/', $data);
  147. return $lines;
  148. }
  149. private function _apply_diff(&$lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size) {
  150. $src_line--;
  151. $dst_line--;
  152. $line = next($lines);
  153. if ($line === false) {
  154. $this->msg = _PHPP_UNEXPECTED_EOF;
  155. return false;
  156. }
  157. $source = array(); // source lines (old file)
  158. $destin = array(); // new lines (new file)
  159. $src_left = $src_size;
  160. $dst_left = $dst_size;
  161. do {
  162. if (!isset($line{0})) {
  163. $source[] = '';
  164. $destin[] = '';
  165. $src_left--;
  166. $dst_left--;
  167. continue;
  168. }
  169. if ($line{0}=='-') {
  170. if ($src_left==0) {
  171. $this->msg = sprintf(_PHPP_UNEXPECTED_REMOVE_LINE, key($lines));
  172. return false;
  173. }
  174. $source[] = substr($line, 1);
  175. $src_left--;
  176. } else if ($line{0}=='+') {
  177. if ($dst_left==0) {
  178. $this->msg = sprintf(_PHPP_UNEXPECTED_ADD_LINE, key($lines));
  179. return false;
  180. }
  181. $destin[] = substr($line, 1);
  182. $dst_left--;
  183. } else {
  184. if (!isset($line{1}))
  185. $line = '';
  186. else if ($line{0}=='\\') {
  187. if ($line=='\\ No newline at end of file') {
  188. continue;
  189. }
  190. } else {
  191. $line = substr($line, 1);
  192. }
  193. $source[] = $line;
  194. $destin[] = $line;
  195. $src_left--;
  196. $dst_left--;
  197. }
  198. if (($src_left==0) && ($dst_left==0)) {
  199. // now apply the patch, finally!
  200. if ($src_size>0) {
  201. $src_lines =& $this->_get_source($src);
  202. if (!isset($src_lines)) {
  203. return false;
  204. }
  205. }
  206. if ($dst_size>0) {
  207. if ($src_size>0) {
  208. $dst_lines =& $this->_get_destin($dst, $src);
  209. if (!isset($dst_lines))
  210. return false;
  211. $src_bottom=$src_line+count($source);
  212. $dst_bottom=$dst_line+count($destin);
  213. for ($l=$src_line;$l<$src_bottom;$l++) {
  214. if ($src_lines[$l]!=$source[$l-$src_line]) {
  215. $this->msg = sprintf(_PHPP_FAILED_VERIFY, $src, $l);
  216. return false;
  217. }
  218. }
  219. array_splice($dst_lines, $dst_line, count($source), $destin);
  220. } else
  221. $this->destinations[$dst] = $destin;
  222. } else
  223. $this->removals[] = $src;
  224. return true;
  225. }
  226. } while (FALSE !== ($line = next($lines)));
  227. $this->msg = _PHPP_UNEXPECTED_EOF;
  228. return false;
  229. }
  230. }
  231. ?>