##// END OF EJS Templates
added changeset review status into repo/branch compare view
marcink -
r2393:b94eac50 codereview
parent child Browse files
Show More
@@ -1,140 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.compare
3 rhodecode.controllers.compare
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 compare controller for pylons showoing differences between two
6 compare controller for pylons showoing differences between two
7 repos, branches, bookmarks or tips
7 repos, branches, bookmarks or tips
8
8
9 :created_on: May 6, 2012
9 :created_on: May 6, 2012
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import binascii
28 import binascii
29
29
30 from webob.exc import HTTPNotFound
30 from webob.exc import HTTPNotFound
31 from pylons import request, response, session, tmpl_context as c, url
31 from pylons import request, response, session, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33
33
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib import diffs
37 from rhodecode.lib import diffs
38
38
39 from rhodecode.model.db import Repository
39 from rhodecode.model.db import Repository
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class CompareController(BaseRepoController):
44 class CompareController(BaseRepoController):
45
45
46 @LoginRequired()
46 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 'repository.admin')
48 'repository.admin')
49 def __before__(self):
49 def __before__(self):
50 super(CompareController, self).__before__()
50 super(CompareController, self).__before__()
51
51
52
53 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
52 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
54 from mercurial import discovery
53 from mercurial import discovery
55 other = org_repo._repo
54 other = org_repo._repo
56 repo = other_repo._repo
55 repo = other_repo._repo
57 tip = other[org_ref[1]]
56 tip = other[org_ref[1]]
58 log.debug('Doing discovery for %s@%s vs %s@%s' % (
57 log.debug('Doing discovery for %s@%s vs %s@%s' % (
59 org_repo, org_ref, other_repo, other_ref)
58 org_repo, org_ref, other_repo, other_ref)
60 )
59 )
61 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
60 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
62 tmp = discovery.findcommonincoming(
61 tmp = discovery.findcommonincoming(
63 repo=repo, # other_repo we check for incoming
62 repo=repo, # other_repo we check for incoming
64 remote=other, # org_repo source for incoming
63 remote=other, # org_repo source for incoming
65 heads=[tip.node()],
64 heads=[tip.node()],
66 force=False
65 force=False
67 )
66 )
68 return tmp
67 return tmp
69
68
70 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
69 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
71 changesets = []
70 changesets = []
72 #case two independent repos
71 #case two independent repos
73 if org_repo != other_repo:
72 if org_repo != other_repo:
74 common, incoming, rheads = tmp
73 common, incoming, rheads = tmp
75
74
76 if not incoming:
75 if not incoming:
77 revs = []
76 revs = []
78 else:
77 else:
79 revs = org_repo._repo.changelog.findmissing(common, rheads)
78 revs = org_repo._repo.changelog.findmissing(common, rheads)
80
79
81 for cs in reversed(map(binascii.hexlify, revs)):
80 for cs in reversed(map(binascii.hexlify, revs)):
82 changesets.append(org_repo.get_changeset(cs))
81 changesets.append(org_repo.get_changeset(cs))
83 else:
82 else:
84 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
83 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
85 other_ref[1])]
84 other_ref[1])]
86 from mercurial import scmutil
85 from mercurial import scmutil
87 out = scmutil.revrange(org_repo._repo, revs)
86 out = scmutil.revrange(org_repo._repo, revs)
88 for cs in reversed(out):
87 for cs in reversed(out):
89 changesets.append(org_repo.get_changeset(cs))
88 changesets.append(org_repo.get_changeset(cs))
90
89
91 return changesets
90 return changesets
92
91
93 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
92 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
94
93
95 org_repo = c.rhodecode_db_repo.repo_name
94 org_repo = c.rhodecode_db_repo.repo_name
96 org_ref = (org_ref_type, org_ref)
95 org_ref = (org_ref_type, org_ref)
97 other_ref = (other_ref_type, other_ref)
96 other_ref = (other_ref_type, other_ref)
98 other_repo = request.GET.get('repo', org_repo)
97 other_repo = request.GET.get('repo', org_repo)
99
98
100 c.swap_url = h.url('compare_url', repo_name=other_repo,
99 c.swap_url = h.url('compare_url', repo_name=other_repo,
101 org_ref_type=other_ref[0], org_ref=other_ref[1],
100 org_ref_type=other_ref[0], org_ref=other_ref[1],
102 other_ref_type=org_ref[0], other_ref=org_ref[1],
101 other_ref_type=org_ref[0], other_ref=org_ref[1],
103 repo=org_repo)
102 repo=org_repo)
104
103
105 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
104 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
106 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
105 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
107
106
108 if c.org_repo is None or c.other_repo is None:
107 if c.org_repo is None or c.other_repo is None:
109 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
108 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
110 raise HTTPNotFound
109 raise HTTPNotFound
111
110
112 discovery_data = self._get_discovery(org_repo.scm_instance,
111 discovery_data = self._get_discovery(org_repo.scm_instance,
113 org_ref,
112 org_ref,
114 other_repo.scm_instance,
113 other_repo.scm_instance,
115 other_ref)
114 other_ref)
116 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
115 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
117 org_ref,
116 org_ref,
118 other_repo.scm_instance,
117 other_repo.scm_instance,
119 other_ref,
118 other_ref,
120 discovery_data)
119 discovery_data)
121
120
121 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
122 c.cs_ranges])
123
124
122 c.org_ref = org_ref[1]
125 c.org_ref = org_ref[1]
123 c.other_ref = other_ref[1]
126 c.other_ref = other_ref[1]
124 # diff needs to have swapped org with other to generate proper diff
127 # diff needs to have swapped org with other to generate proper diff
125 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
128 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
126 discovery_data)
129 discovery_data)
127 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
130 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
128 _parsed = diff_processor.prepare()
131 _parsed = diff_processor.prepare()
129
132
130 c.files = []
133 c.files = []
131 c.changes = {}
134 c.changes = {}
132 # sort Added first then Modified last Deleted files
135
133 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
136 for f in _parsed:
134 for f in sorted(_parsed, key=sorter):
135 fid = h.FID('', f['filename'])
137 fid = h.FID('', f['filename'])
136 c.files.append([fid, f['operation'], f['filename'], f['stats']])
138 c.files.append([fid, f['operation'], f['filename'], f['stats']])
137 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
139 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
138 c.changes[fid] = [f['operation'], f['filename'], diff]
140 c.changes[fid] = [f['operation'], f['filename'], diff]
139
141
140 return render('compare/compare_diff.html')
142 return render('compare/compare_diff.html')
@@ -1,90 +1,90 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
5 ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(u'Home',h.url('/'))}
9 ${h.link_to(u'Home',h.url('/'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 &raquo;
12 &raquo;
13 ${_('Compare')}
13 ${_('Compare')}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('changelog')}
17 ${self.menu('changelog')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <div class="table">
26 <div class="table">
27 <div id="body" class="diffblock">
27 <div id="body" class="diffblock">
28 <div class="code-header cv">
28 <div class="code-header cv">
29 <h3 class="code-header-title">${_('Compare View')}</h3>
29 <h3 class="code-header-title">${_('Compare View')}</h3>
30 <div>
30 <div>
31 ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a>
31 ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a>
32 </div>
32 </div>
33 </div>
33 </div>
34 </div>
34 </div>
35 <div id="changeset_compare_view_content">
35 <div id="changeset_compare_view_content">
36 <div class="container">
36 <div class="container">
37 <table class="compare_view_commits noborder">
37 <table class="compare_view_commits noborder">
38 %for cnt, cs in enumerate(c.cs_ranges):
38 %for cnt, cs in enumerate(c.cs_ranges):
39 <tr>
39 <tr>
40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
41 <td>
42 %if cs.raw_id in c.statuses:
43 <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
44 %endif
45 </td>
41 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
46 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
42 <td><div class="author">${h.person(cs.author)}</div></td>
47 <td><div class="author">${h.person(cs.author)}</div></td>
43 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
48 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
44 <td>
45 %if hasattr(c,'statuses') and c.statuses:
46 <div title="${_('Changeset status')}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cnt])}" /></div>
47 %endif
48 </td>
49 <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
49 <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
50 </tr>
50 </tr>
51 %endfor
51 %endfor
52 </table>
52 </table>
53 </div>
53 </div>
54 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
54 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
55 <div class="cs_files">
55 <div class="cs_files">
56 %for fid, change, f, stat in c.files:
56 %for fid, change, f, stat in c.files:
57 <div class="cs_${change}">
57 <div class="cs_${change}">
58 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
58 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
59 <div class="changes">${h.fancy_file_stats(stat)}</div>
59 <div class="changes">${h.fancy_file_stats(stat)}</div>
60 </div>
60 </div>
61 %endfor
61 %endfor
62 </div>
62 </div>
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 ## diff block
66 ## diff block
67 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
67 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
68 %for fid, change, f, stat in c.files:
68 %for fid, change, f, stat in c.files:
69 ${diff_block.diff_block_simple([c.changes[fid]])}
69 ${diff_block.diff_block_simple([c.changes[fid]])}
70 %endfor
70 %endfor
71
71
72 <script type="text/javascript">
72 <script type="text/javascript">
73
73
74 YUE.onDOMReady(function(){
74 YUE.onDOMReady(function(){
75
75
76 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
76 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
77 var act = e.currentTarget.nextElementSibling;
77 var act = e.currentTarget.nextElementSibling;
78
78
79 if(YUD.hasClass(act,'active')){
79 if(YUD.hasClass(act,'active')){
80 YUD.removeClass(act,'active');
80 YUD.removeClass(act,'active');
81 YUD.setStyle(act,'display','none');
81 YUD.setStyle(act,'display','none');
82 }else{
82 }else{
83 YUD.addClass(act,'active');
83 YUD.addClass(act,'active');
84 YUD.setStyle(act,'display','');
84 YUD.setStyle(act,'display','');
85 }
85 }
86 });
86 });
87 })
87 })
88 </script>
88 </script>
89 </div>
89 </div>
90 </%def>
90 </%def>
General Comments 0
You need to be logged in to leave comments. Login now