From: pk910 Date: Fri, 15 Feb 2013 07:42:56 +0000 (+0100) Subject: added commit difftree X-Git-Url: http://git.pk910.de/?p=phpgitweb.git;a=commitdiff_plain;h=3225d787391540a52b39a34ca809a71d443b49f0 added commit difftree --- diff --git a/htdocs/config.example.php b/htdocs/config.example.php index 6b3bdc4..247833f 100644 --- a/htdocs/config.example.php +++ b/htdocs/config.example.php @@ -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 diff --git a/htdocs/lib/GitCommand.class.php b/htdocs/lib/GitCommand.class.php index cb63050..c06e0fb 100644 --- a/htdocs/lib/GitCommand.class.php +++ b/htdocs/lib/GitCommand.class.php @@ -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; + } + } diff --git a/htdocs/lib/Tools.class.php b/htdocs/lib/Tools.class.php index 6f28961..c0b01b0 100644 --- a/htdocs/lib/Tools.class.php +++ b/htdocs/lib/Tools.class.php @@ -17,25 +17,28 @@ */ 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(); diff --git a/htdocs/pages/commit.class.php b/htdocs/pages/commit.class.php index 17f5678..3c10b44 100644 --- a/htdocs/pages/commit.class.php +++ b/htdocs/pages/commit.class.php @@ -18,6 +18,157 @@ 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 diff --git a/htdocs/templates/default/commit.tpl b/htdocs/templates/default/commit.tpl index cb2fc4e..c77e905 100644 --- a/htdocs/templates/default/commit.tpl +++ b/htdocs/templates/default/commit.tpl @@ -49,9 +49,7 @@
- -%tree% -
+%difftree% # [parent] @@ -76,12 +74,76 @@ # [commit_ref_tag] %name% +# [difftree] + +%tree% +
+ # [tree] - src/mod-helpserv.c - - - diff | - blob | - history + %file% + %specials% + %merge% + %links% + +# [tree_merge] + %links% +# [tree_merge_new] + | +# [tree_merge_blob] +blob +# [tree_merge_history] +history +# [tree_merge_diff] +diff%id% | +# [tree_merge_patch_link] + patch | + + +# [tree_file_link] +%file% + +# [tree_patch_link] +patch | + +# [tree_new] +[new %type%] + +# [tree_new_file] +[new %type% with mode: %mode%] + +# [tree_new_links] +blob + +# [tree_deleted] +[deleted %type%] + +# [tree_deleted_links] +blob | history + +# [tree_changed] +[changed%changes%] +# [tree_changed_type] + from %from% to %to% +# [tree_changed_mode] + mode: %from%->%to% +# [tree_changed_mode_to] + mode: %to% + +# [tree_changed_links_diff] +diff | +# [tree_changed_links] +blob | history + +# [tree_moved] +[moved from %file% with %similarity% similarity%mode%] +# [tree_copied] +[copied from %file% with %similarity% similarity%mode%] +# [tree_moved_mode] +, mode: %mode% + +# [tree_moved_links_diff] +diff | +# [tree_moved_links] +blob | history