added commit difftree
authorpk910 <philipp@zoelle1.de>
Fri, 15 Feb 2013 07:42:56 +0000 (08:42 +0100)
committerpk910 <philipp@zoelle1.de>
Fri, 15 Feb 2013 07:42:56 +0000 (08:42 +0100)
htdocs/config.example.php
htdocs/lib/GitCommand.class.php
htdocs/lib/Tools.class.php
htdocs/pages/commit.class.php
htdocs/templates/default/commit.tpl

index 6b3bdc4656dcc5891e6e7d8ab03c7cc2c6f3f3f4..247833f1f8521340f276fe1757af1d5a597edf3e 100644 (file)
@@ -57,6 +57,23 @@ class GitConfig {
        const GITGRAPH_END_SIZE = 20;
        const GITGRAPH_TILE_SIZE = 20;
        const GITGRAPH_BASE64 = true;
+       
+       /* rename detection options for git-diff and git-diff-tree
+       * - default is '0', with the cost proportional to
+       *   (number of removed files) * (number of new files).
+       *   can detect renames of files
+       * - more costly is '1' (which implies '0'), with the cost proportional to
+       *   (number of changed files + number of removed files) * (number of new files)
+       *   can detect renames and copies of changed files
+       * - even more costly is '2' (which implies '0', '1'), with cost
+       *   (number of files in the original tree) * (number of new files)
+       *   can detect renames and copies of all files
+       *
+       * DETECT_REWRITES enables the additional detection for complete rewrites
+       */
+       const DETECT_RENAME_LEVEL = 0;
+       const DETECT_REWRITES = false;
+       
 }
 
 ?>
\ No newline at end of file
index cb63050a46612cd0f1569ffd885d9fb8b2b6efdc..c06e0fb4298fe120c39370e079d03ffa28890fb2 100644 (file)
@@ -138,4 +138,64 @@ class GitCommand {
                return null;
        }
        
+       public static function get_commit_changes($git_path, $commit_id, $parents) {
+               $args = array("diff-tree", "-r", "--no-commit-id");
+               switch(GitConfig::DETECT_RENAME_LEVEL) {
+               case 0:
+                       $args[] = "-M";
+                       break;
+               case 1:
+                       $args[] = "-C";
+                       break;
+               case 2:
+                       $args[] = "-C";
+                       $args[] = "--find-copies-harder";
+                       break;
+               }
+               if(GitConfig::DETECT_REWRITES)
+                       $args[] = "-B";
+               if(is_array($parents) && count($parents) > 1)
+                       $args[] = "-c";
+               else if(is_array($parents) && count($parents) == 1)
+                       $args[] = $parents[0];
+               else
+                       $args[] = "--root";
+               $args[] = $commit_id;
+               $args[] = "--";
+               $result = self::git_execute($args, $git_path);
+               $tree = array();
+               foreach(explode("\n", $result) as $line) {
+                       if($line == "")
+                               continue;
+                       $entry = array();
+                       if(preg_match('/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/i', $line, $matches)) {
+                               $entry['parents'] = 1;
+                               $entry['from_mode'] = $matches[1];
+                               $entry['to_mode'] = $matches[2];
+                               $entry['from_id'] = $matches[3];
+                               $entry['to_id'] = $matches[4];
+                               $entry['status'] = $matches[5];
+                               $entry['similarity'] = $matches[6];
+                               $entry['file'] = $matches[7];
+                               if($entry['status'] == 'R' || $entry['status'] == 'C') { //renamed or copied
+                                       $files = explode("\t", $entry['file']);
+                                       $entry['from_file'] = $files[0];
+                                       $entry['to_file'] = $files[1];
+                               }
+                       } else if(preg_match('/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$/i', $line, $matches)) {
+                               $entry['parents'] = strlen($matches[1]);
+                               $from_modes = explode(" ", $matches[2]);
+                               $entry['from_mode'] = array_slice($from_modes, 1);
+                               $entry['to_mode'] = $from_modes[0];
+                               $from_ids = explode(" ", $matches[3]);
+                               $entry['from_id'] = array_slice($from_ids, 1);
+                               $entry['to_id'] = $from_ids[0];
+                               $entry['status'] = str_split($matches[4]);
+                               $entry['file'] = $matches[5];
+                       }
+                       $tree[] = $entry;
+               }
+               return $tree;
+       }
+       
 }
index 6f28961d8d1612e9c11df2c074e386807b07283d..c0b01b0cd15b406e931145b479073f44b3b0589b 100644 (file)
  */
 
 class Tools {
-       const S_IFMT      = 170000;
-       const S_IFGITLINK = 160000;
-       const S_IFLNK     = 120000;
-       const S_IFREG     = 100000;
-       const S_IFDIR     =  40000;
-       const S_IFINVALID =  30000;
-       const S_IXUSR     =    100;
+       /* octal constants (sys/stat.h) */
+       const S_IFMT      = 0170000;
+       const S_IFGITLINK = 0160000; /* GIT defined */
+       const S_IFLNK     = 0120000;
+       const S_IFREG     = 0100000;
+       const S_IFDIR     = 0040000;
+       const S_IPERMS    = 0000777;
+       const S_IXUSR     = 0000100;
        
        public static function get_filetype($mode, $exec = false) {
                if(!preg_match('/^[0-7]+$/', $mode))
                        return $mode;
                
-               if(($mode & self::S_IFMT) == S_IFGITLINK)
+               $mode = octdec($mode);
+               
+               if(($mode & self::S_IFMT) == self::S_IFGITLINK)
                        return 'submodule';
-               else if(($mode & self::S_IFMT) == S_IFDIR)
+               else if(($mode & self::S_IFMT) == self::S_IFDIR)
                        return 'directory';
-               else if(($mode & self::S_IFMT) == S_IFLNK)
+               else if(($mode & self::S_IFMT) == self::S_IFLNK)
                        return 'symlink';
-               else if(($mode & self::S_IFMT) == S_IFREG) {
+               else if(($mode & self::S_IFMT) == self::S_IFREG) {
                        if($exec && ($mode & self::S_IXUSR))
                                return 'executable';
                        return 'file';
@@ -43,6 +46,16 @@ class Tools {
                        return 'unknown';
        }
        
+       public static function is_regular_file($mode) {
+               $mode = octdec($mode);
+               return (($mode & self::S_IFMT) == self::S_IFREG);
+       }
+       
+       public static function get_file_permissions($mode) {
+               $mode = octdec($mode);
+               return sprintf("%04o",($mode & self::S_IPERMS));
+       }
+       
        public static function age_calculate($last_change) {
                $result = array();
                $now = time();
index 17f5678758cf9312409876c3ec94ce5838a1fb4b..3c10b4486d1798bb7675eaac08b9de1863202158 100644 (file)
 
 require_once('pages/shortlog.class.php');
 
+class difftree {
+       
+       public function generate_difftree($project, $commit, $patch_link) {
+               $difftree = new ContentProvider('commit', 'difftree');
+               $tree = GitCommand::get_commit_changes($project['path'], $commit['id'], $commit['parent']);
+               $entry_count = 0;
+               if(count($commit['parent']) > 1)
+                       $difftree->set('class', ' combined');
+               else
+                       $difftree->set('class', '');
+               foreach($tree as $entry) {
+                       $entry_count++;
+                       $difftree->append('tree', $this->tree_entry($entry_count, (($entry_count % 2) ? 'dark' : 'light'), $commit, $entry, $patch_link));
+               }
+               if(count($tree) == 0)
+                       $difftree->set('tree', '');
+               return $difftree;
+       }
+       
+       private function tree_entry($diffid, $class, $commit, $entry, $patch_link) {
+               $tree = new ContentProvider('commit', 'tree');
+               $tree->set('class', $class);
+               $tree->set('hash', $commit['id']);
+               $tree->set('file', $entry['file']);
+               
+               if(count($commit['parent']) > 1) {
+                       if(preg_match('/^[0]{40}$/', $entry['to_id'])) //file doesn't exist in the result (child) commit
+                               $tree->set('file', $entry['file']);
+                       else
+                               $tree->set('file', new ContentProvider('commit', 'tree_file_link', array('file' => $entry['file'])));
+                       
+                       $tree->set('specials', '');
+                       
+                       if($patch_link)
+                               $tree->append('merge', new ContentProvider('commit', 'tree_merge_patch_link', array("patch_marker" => "patch".$diffid)));
+                       
+                       $has_history = false;
+                       $not_deleted = false;
+                       
+                       for($i = 0; $i < count($commit['parent']); $i++) {
+                               $status = $entry['status'][$i];
+                               $merge = null;
+                               
+                               $has_history = ($has_history || ($status != 'A'));
+                               $not_deleted = ($not_deleted || ($status != 'D'));
+                               
+                               switch($status) {
+                               case 'A': //Added
+                                       $merge = new ContentProvider('commit', 'tree_merge_new');
+                                       break;
+                               case 'D': //Deleted
+                                       $merge = new ContentProvider('commit', 'tree_merge');
+                                       $merge->set('class', '');
+                                       $blob_link = new ContentProvider('commit', 'tree_merge_blob', array('hash' => $commit['parent'][$i], 'file' => $entry['file']));
+                                       $merge->set('links', array($blob_link, ' | '));
+                                       break;
+                               default:
+                                       $merge = new ContentProvider('commit', 'tree_merge');
+                                       if($entry['from_id'][$i] == $entry['to_id']) {
+                                               $merge->set('class', ' nochange');
+                                               $merge->set('links', ' | ');
+                                       } else {
+                                               $merge->set('class', '');
+                                               $merge->set('links', new ContentProvider('commit', 'tree_merge_diff', array('hash' => $commit['id'], 'parent' => $commit['parent'][$i], 'file' => $entry['file'], 'id' => ($i + 1))));
+                                       }
+                               }
+                               $tree->append('merge', $merge);
+                       }
+                       
+                       $tree->set('links', '');
+                       if($not_deleted) {
+                               $tree->append('links', new ContentProvider('commit', 'tree_merge_blob', array('hash' => $commit['id'], 'file' => $entry['file'])));
+                               if($has_history)
+                                       $tree->append('links', ' | ');
+                       }
+                       if($has_history)
+                               $tree->append('links', new ContentProvider('commit', 'tree_merge_history', array('hash' => $commit['id'], 'file' => $entry['file'])));
+                       
+               } else {
+                       $tree->set('file', new ContentProvider('commit', 'tree_file_link', array('file' => $entry['file'])));
+                       $tree->set('merge', '');
+                       
+                       $from_type = Tools::get_filetype($entry['from_mode']);
+                       $to_type = Tools::get_filetype($entry['to_mode']);
+                       
+                       $from_mode = (Tools::is_regular_file($entry['from_mode']) ? Tools::get_file_permissions($entry['from_mode']) : null);
+                       $to_mode = (Tools::is_regular_file($entry['to_mode']) ? Tools::get_file_permissions($entry['to_mode']) : null);
+                       
+                       $link_placeholders = array(
+                                       "hash" => $commit['id'],
+                                       "file" => $entry['file'],
+                                       "parent" => (count($commit['parent']) ? $commit['parent'][0] : ""),
+                               );
+                       if($patch_link)
+                               $tree->append('links', new ContentProvider('commit', 'tree_patch_link', array("patch_marker" => "patch".$diffid)));
+                       
+                       switch($entry['status']) {
+                       case 'A': //Added
+                               $tree->set('specials', new ContentProvider('commit', (Tools::is_regular_file($entry['to_mode']) ? 'tree_new_file' : 'tree_new'), array('type' => $to_type, 'mode' => $to_mode)));
+                               $tree->append('links', new ContentProvider('commit', 'tree_new_links', $link_placeholders));
+                               break;
+                       case 'D': //Deleted
+                               $tree->set('specials', new ContentProvider('commit', 'tree_deleted', array('type' => $from_type)));
+                               $tree->append('links', new ContentProvider('commit', 'tree_deleted_links', $link_placeholders));
+                               break;
+                       case 'M': //Modified
+                       case 'T': //Type changed
+                               if($entry['from_mode'] != $entry['to_mode']) {
+                                       $modified = new ContentProvider('commit', 'tree_changed');
+                                       $tree->set('specials', $modified);
+                                       if($from_type != $to_type)
+                                               $modified->append('changes', new ContentProvider('commit', 'tree_changed_type', array('from' => $from_type, 'to' => $to_type)));
+                                       if($from_mode != $to_mode && $to_mode) {
+                                               if($from_mode)
+                                                       $modified->append('changes', new ContentProvider('commit', 'tree_changed_mode', array('from' => $from_mode, 'to' => $to_mode)));
+                                               else
+                                                       $modified->append('changes', new ContentProvider('commit', 'tree_changed_mode_to', array('to' => $to_mode)));
+                                       }
+                               } else
+                                       $tree->set('specials', '');
+                               if($entry['from_id'] != $entry['to_id'])
+                                       $tree->append('links', new ContentProvider('commit', 'tree_changed_links_diff', $link_placeholders));
+                               $tree->append('links', new ContentProvider('commit', 'tree_changed_links', $link_placeholders));
+                               break;
+                       case 'R': //Renamed
+                       case 'C': //Copied
+                               $actions = array('R' => 'tree_moved', 'C' => 'tree_copied');
+                               $move = new ContentProvider('commit', $actions[$entry['status']]);
+                               $tree->set('specials', $move);
+                               $tree->set('file', $entry['to_file']);
+                               $move->set('file', $entry['from_file']);
+                               $move->set('hash', $commit['parent'][0]);
+                               $move->set('similarity', $entry['similarity']);
+                               if($from_mode != $to_mode)
+                                       $move->set('mode', new ContentProvider('commit', 'tree_moved_mode', array('mode' => $to_mode)));
+                               else
+                                       $move->set('mode', '');
+                               if($entry['from_id'] != $entry['to_id'])
+                                       $tree->append('links', new ContentProvider('commit', 'tree_moved_links_diff', $link_placeholders));
+                               $tree->append('links', new ContentProvider('commit', 'tree_moved_links', $link_placeholders));
+                               break;
+                       default:
+                               
+                       }
+                       
+               }
+               return $tree;
+       }
+       
+}
+
 class page_commit {
     private $page, $phpgitweb;
        private $commitid;
@@ -70,6 +221,8 @@ class page_commit {
                foreach($commit['parent'] as $parent) {
                        $this->page->append('parents', new ContentProvider('commit', 'parent', array('hash' => $parent, 'head' => $commit['id'])));
                }
+               if(count($commit['parent']) == 0)
+                       $this->page->set('parents', '');
                
                $refs = new ContentProvider('commit', 'commit_refs');
                $found = false;
@@ -89,9 +242,14 @@ class page_commit {
                }
                $this->page->set('refs', ($found ? $refs : ""));
                
+               $difftree = new difftree();
+               $this->page->set('difftree', $difftree->generate_difftree($project, $commit, false));
+               
                return $this->page;
        }
        
+       
+       
 }
 
 ?>
\ No newline at end of file
index cb2fc4eea8237b82fe86049cd3ba3fda7d114204..c77e90599750294b50eff7ced4a3b1b9d3439be2 100644 (file)
@@ -49,9 +49,7 @@
 
 <div class="list_head">
 </div>
-<table class="diff_tree">
-%tree%
-</table>
+%difftree%
 
 
 # [parent]
 # [commit_ref_tag]
 <span class="tag" title="tags/%name%">%name%</span>
 
+# [difftree]
+<table class="diff_tree%class%">
+%tree%
+</table>
+
 # [tree]
 <tr class="%class%">
-       <td><a class="list" href="?p=%project%&a=blob&h=%hash%&f=%file%">src/mod-helpserv.c</a></td>
-       <td></td>
-       <td class="link">
-               <a href="?p=%project%&a=blobdiff&h=%hash%&f=src/mod-helpserv.c&hp=%parent%">diff</a> | 
-               <a href="?p=%project%&a=blob&h=%hash%&f=%file%">blob</a> | 
-               <a href="?p=%project%&a=history&h=%hash%&f=%file%">history</a></td>
+       <td>%file%</td>
+       <td>%specials%</td>
+       %merge%
+       <td class="link">%links%</td>
 </tr>
+
+# [tree_merge]
+       <td class="link%class%">%links%</td>
+# [tree_merge_new]
+       <td class="link" align="right"> | </td>
+# [tree_merge_blob]
+<a href="?p=%project%&a=blob&h=%hash%&f=%file%">blob</a>
+# [tree_merge_history]
+<a href="?p=%project%&a=history&h=%hash%&f=%file%">history</a>
+# [tree_merge_diff]
+<a href="?p=%project%&a=blobdiff&h=%hash%&f=%file%&hp=%parent%">diff%id%</a> | 
+# [tree_merge_patch_link]
+       <td class="link"><a href="#%patch_marker%">patch</a> | </td>
+
+
+# [tree_file_link]
+<a class="list" href="?p=%project%&a=blob&h=%hash%&f=%file%">%file%</a>
+
+# [tree_patch_link]
+<a href="#%patch_marker%">patch</a> | 
+
+# [tree_new]
+<span class="file_status new">[new %type%]</span>
+
+# [tree_new_file]
+<span class="file_status new">[new %type% with mode: %mode%]</span>
+
+# [tree_new_links]
+<a href="?p=%project%&a=blob&h=%hash%&f=%file%">blob</a>
+
+# [tree_deleted]
+<span class="file_status deleted">[deleted %type%]</span>
+
+# [tree_deleted_links]
+<a href="?p=%project%&a=blob&h=%hash%&f=%file%">blob</a> | <a href="?p=%project%&a=history&h=%hash%&f=%file%">history</a>
+
+# [tree_changed]
+<span class="file_status mode_chnge">[changed%changes%]</span>
+# [tree_changed_type]
+ from %from% to %to%
+# [tree_changed_mode]
+ mode: %from%->%to%
+# [tree_changed_mode_to]
+ mode: %to%
+
+# [tree_changed_links_diff]
+<a href="?p=%project%&a=blobdiff&h=%hash%&f=%file%&hp=%parent%">diff</a> | 
+# [tree_changed_links]
+<a href="?p=%project%&a=blob&h=%hash%&f=%file%">blob</a> | <a href="?p=%project%&a=history&h=%hash%&f=%file%">history</a>
+
+# [tree_moved]
+<span class="file_status moved">[moved from <a class="list" href="?p=%project%&a=blob&f=%file%&h=%hash%">%file%</a> with %similarity% similarity%mode%]</span>
+# [tree_copied]
+<span class="file_status copied">[copied from <a class="list" href="?p=%project%&a=blob&f=%file%&h=%hash%">%file%</a> with %similarity% similarity%mode%]</span>
+# [tree_moved_mode]
+, mode: %mode%
+
+# [tree_moved_links_diff]
+<a href="?p=%project%&a=blobdiff&h=%hash%&f=src/mod-helpserv.c&hp=%parent%">diff</a> | 
+# [tree_moved_links]
+<a href="?p=%project%&a=blob&h=%hash%&f=%file%">blob</a> | <a href="?p=%project%&a=history&h=%hash%&f=%file%">history</a>