##// END OF EJS Templates
compare and diff: remove unused "bundle" functionality...
Mads Kiilerich -
r3304:70309536 beta
parent child Browse files
Show More
@@ -1,183 +1,180 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 showing 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
28
29 from webob.exc import HTTPNotFound
29 from webob.exc import HTTPNotFound
30 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, session, tmpl_context as c, url
31 from pylons.controllers.util import abort, redirect
31 from pylons.controllers.util import abort, redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib import diffs
38 from rhodecode.lib import diffs
39
39
40 from rhodecode.model.db import Repository
40 from rhodecode.model.db import Repository
41 from rhodecode.model.pull_request import PullRequestModel
41 from rhodecode.model.pull_request import PullRequestModel
42 from webob.exc import HTTPBadRequest
42 from webob.exc import HTTPBadRequest
43 from rhodecode.lib.utils2 import str2bool
43 from rhodecode.lib.utils2 import str2bool
44 from rhodecode.lib.diffs import LimitedDiffContainer
44 from rhodecode.lib.diffs import LimitedDiffContainer
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class CompareController(BaseRepoController):
50 class CompareController(BaseRepoController):
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 'repository.admin')
54 'repository.admin')
55 def __before__(self):
55 def __before__(self):
56 super(CompareController, self).__before__()
56 super(CompareController, self).__before__()
57
57
58 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
58 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
59 partial=False):
59 partial=False):
60 """
60 """
61 Safe way to get changeset if error occur it redirects to changeset with
61 Safe way to get changeset if error occur it redirects to changeset with
62 proper message. If partial is set then don't do redirect raise Exception
62 proper message. If partial is set then don't do redirect raise Exception
63 instead
63 instead
64
64
65 :param rev: revision to fetch
65 :param rev: revision to fetch
66 :param repo: repo instance
66 :param repo: repo instance
67 """
67 """
68
68
69 try:
69 try:
70 type_, rev = rev
70 type_, rev = rev
71 return repo.scm_instance.get_changeset(rev)
71 return repo.scm_instance.get_changeset(rev)
72 except EmptyRepositoryError, e:
72 except EmptyRepositoryError, e:
73 if not redirect_after:
73 if not redirect_after:
74 return None
74 return None
75 h.flash(h.literal(_('There are no changesets yet')),
75 h.flash(h.literal(_('There are no changesets yet')),
76 category='warning')
76 category='warning')
77 redirect(url('summary_home', repo_name=repo.repo_name))
77 redirect(url('summary_home', repo_name=repo.repo_name))
78
78
79 except RepositoryError, e:
79 except RepositoryError, e:
80 log.error(traceback.format_exc())
80 log.error(traceback.format_exc())
81 h.flash(str(e), category='warning')
81 h.flash(str(e), category='warning')
82 if not partial:
82 if not partial:
83 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 redirect(h.url('summary_home', repo_name=repo.repo_name))
84 raise HTTPBadRequest()
84 raise HTTPBadRequest()
85
85
86 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
86 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
87
87
88 org_repo = c.rhodecode_db_repo.repo_name
88 org_repo = c.rhodecode_db_repo.repo_name
89 org_ref = (org_ref_type, org_ref)
89 org_ref = (org_ref_type, org_ref)
90 other_ref = (other_ref_type, other_ref)
90 other_ref = (other_ref_type, other_ref)
91 other_repo = request.GET.get('repo', org_repo)
91 other_repo = request.GET.get('repo', org_repo)
92 incoming_changesets = str2bool(request.GET.get('bundle', False))
93 c.fulldiff = fulldiff = request.GET.get('fulldiff')
92 c.fulldiff = fulldiff = request.GET.get('fulldiff')
94 rev_start = request.GET.get('rev_start')
93 rev_start = request.GET.get('rev_start')
95 rev_end = request.GET.get('rev_end')
94 rev_end = request.GET.get('rev_end')
96
95
97 c.swap_url = h.url('compare_url', repo_name=other_repo,
96 c.swap_url = h.url('compare_url', repo_name=other_repo,
98 org_ref_type=other_ref[0], org_ref=other_ref[1],
97 org_ref_type=other_ref[0], org_ref=other_ref[1],
99 other_ref_type=org_ref[0], other_ref=org_ref[1],
98 other_ref_type=org_ref[0], other_ref=org_ref[1],
100 repo=org_repo, as_form=request.GET.get('as_form'),
99 repo=org_repo, as_form=request.GET.get('as_form'))
101 bundle=incoming_changesets)
102
100
103 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
101 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
104 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
102 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
105
103
106 if c.org_repo is None:
104 if c.org_repo is None:
107 log.error('Could not find org repo %s' % org_repo)
105 log.error('Could not find org repo %s' % org_repo)
108 raise HTTPNotFound
106 raise HTTPNotFound
109 if c.other_repo is None:
107 if c.other_repo is None:
110 log.error('Could not find other repo %s' % other_repo)
108 log.error('Could not find other repo %s' % other_repo)
111 raise HTTPNotFound
109 raise HTTPNotFound
112
110
113 if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo):
111 if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo):
114 log.error('compare of two remote repos not available for GIT REPOS')
112 log.error('compare of two remote repos not available for GIT REPOS')
115 raise HTTPNotFound
113 raise HTTPNotFound
116
114
117 if c.org_repo.scm_instance.alias != c.other_repo.scm_instance.alias:
115 if c.org_repo.scm_instance.alias != c.other_repo.scm_instance.alias:
118 log.error('compare of two different kind of remote repos not available')
116 log.error('compare of two different kind of remote repos not available')
119 raise HTTPNotFound
117 raise HTTPNotFound
120
118
121 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
119 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
122 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
120 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
123 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
121 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
124
122
125 if rev_start and rev_end:
123 if rev_start and rev_end:
126 #replace our org_ref with given CS
124 #replace our org_ref with given CS
127 org_ref = ('rev', rev_start)
125 org_ref = ('rev', rev_start)
128 other_ref = ('rev', rev_end)
126 other_ref = ('rev', rev_end)
129
127
130 c.cs_ranges = PullRequestModel().get_compare_data(
128 c.cs_ranges = PullRequestModel().get_compare_data(
131 org_repo, org_ref, other_repo, other_ref,
129 org_repo, org_ref, other_repo, other_ref,
132 )
130 )
133
131
134 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
132 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
135 c.cs_ranges])
133 c.cs_ranges])
136 c.target_repo = c.repo_name
134 c.target_repo = c.repo_name
137 # defines that we need hidden inputs with changesets
135 # defines that we need hidden inputs with changesets
138 c.as_form = request.GET.get('as_form', False)
136 c.as_form = request.GET.get('as_form', False)
139 if partial:
137 if partial:
140 return render('compare/compare_cs.html')
138 return render('compare/compare_cs.html')
141
139
142 c.org_ref = org_ref[1]
140 c.org_ref = org_ref[1]
143 c.other_ref = other_ref[1]
141 c.other_ref = other_ref[1]
144
142
145 if not incoming_changesets and c.cs_ranges and c.org_repo != c.other_repo:
143 if c.cs_ranges and c.org_repo != c.other_repo:
146 # case we want a simple diff without incoming changesets, just
144 # case we want a simple diff without incoming changesets, just
147 # for review purposes. Make the diff on the forked repo, with
145 # for review purposes. Make the diff on the forked repo, with
148 # revision that is common ancestor
146 # revision that is common ancestor
149 _org_ref = org_ref
147 _org_ref = org_ref
150 org_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
148 org_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
151 if c.cs_ranges[0].parents
149 if c.cs_ranges[0].parents
152 else EmptyChangeset(), 'raw_id'))
150 else EmptyChangeset(), 'raw_id'))
153 log.debug('Changed org_ref from %s to %s' % (_org_ref, org_ref))
151 log.debug('Changed org_ref from %s to %s' % (_org_ref, org_ref))
154 other_repo = org_repo
152 other_repo = org_repo
155
153
156 diff_limit = self.cut_off_limit if not fulldiff else None
154 diff_limit = self.cut_off_limit if not fulldiff else None
157
155
158 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref,
156 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
159 remote_compare=incoming_changesets)
160
157
161 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
158 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
162 diff_limit=diff_limit)
159 diff_limit=diff_limit)
163 _parsed = diff_processor.prepare()
160 _parsed = diff_processor.prepare()
164
161
165 c.limited_diff = False
162 c.limited_diff = False
166 if isinstance(_parsed, LimitedDiffContainer):
163 if isinstance(_parsed, LimitedDiffContainer):
167 c.limited_diff = True
164 c.limited_diff = True
168
165
169 c.files = []
166 c.files = []
170 c.changes = {}
167 c.changes = {}
171 c.lines_added = 0
168 c.lines_added = 0
172 c.lines_deleted = 0
169 c.lines_deleted = 0
173 for f in _parsed:
170 for f in _parsed:
174 st = f['stats']
171 st = f['stats']
175 if st[0] != 'b':
172 if st[0] != 'b':
176 c.lines_added += st[0]
173 c.lines_added += st[0]
177 c.lines_deleted += st[1]
174 c.lines_deleted += st[1]
178 fid = h.FID('', f['filename'])
175 fid = h.FID('', f['filename'])
179 c.files.append([fid, f['operation'], f['filename'], f['stats']])
176 c.files.append([fid, f['operation'], f['filename'], f['stats']])
180 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
177 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
181 c.changes[fid] = [f['operation'], f['filename'], diff]
178 c.changes[fid] = [f['operation'], f['filename'], diff]
182
179
183 return render('compare/compare_diff.html')
180 return render('compare/compare_diff.html')
@@ -1,769 +1,716 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.diffs
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Set of diffing helpers, previously part of vcs
6 Set of diffing helpers, previously part of vcs
7
7
8
8
9 :created_on: Dec 4, 2011
9 :created_on: Dec 4, 2011
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 :original copyright: 2007-2008 by Armin Ronacher
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
13 :license: GPLv3, see COPYING for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28 import re
28 import re
29 import difflib
29 import difflib
30 import logging
30 import logging
31 import traceback
32
31
33 from itertools import tee, imap
32 from itertools import tee, imap
34
33
35 from mercurial import patch
36 from mercurial.mdiff import diffopts
37 from mercurial.bundlerepo import bundlerepository
38
39 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
40
35
41 from rhodecode.lib.compat import BytesIO
42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
43 from rhodecode.lib.vcs.exceptions import VCSError
36 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
37 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
38 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.helpers import escape
39 from rhodecode.lib.helpers import escape
47 from rhodecode.lib.utils import make_ui
48 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode
49
41
50 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
51
43
52
44
53 def wrap_to_table(str_):
45 def wrap_to_table(str_):
54 return '''<table class="code-difftable">
46 return '''<table class="code-difftable">
55 <tr class="line no-comment">
47 <tr class="line no-comment">
56 <td class="lineno new"></td>
48 <td class="lineno new"></td>
57 <td class="code no-comment"><pre>%s</pre></td>
49 <td class="code no-comment"><pre>%s</pre></td>
58 </tr>
50 </tr>
59 </table>''' % str_
51 </table>''' % str_
60
52
61
53
62 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
54 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
63 ignore_whitespace=True, line_context=3,
55 ignore_whitespace=True, line_context=3,
64 enable_comments=False):
56 enable_comments=False):
65 """
57 """
66 returns a wrapped diff into a table, checks for cut_off_limit and presents
58 returns a wrapped diff into a table, checks for cut_off_limit and presents
67 proper message
59 proper message
68 """
60 """
69
61
70 if filenode_old is None:
62 if filenode_old is None:
71 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
63 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
72
64
73 if filenode_old.is_binary or filenode_new.is_binary:
65 if filenode_old.is_binary or filenode_new.is_binary:
74 diff = wrap_to_table(_('binary file'))
66 diff = wrap_to_table(_('binary file'))
75 stats = (0, 0)
67 stats = (0, 0)
76 size = 0
68 size = 0
77
69
78 elif cut_off_limit != -1 and (cut_off_limit is None or
70 elif cut_off_limit != -1 and (cut_off_limit is None or
79 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
71 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
80
72
81 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
73 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
82 ignore_whitespace=ignore_whitespace,
74 ignore_whitespace=ignore_whitespace,
83 context=line_context)
75 context=line_context)
84 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
76 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
85
77
86 diff = diff_processor.as_html(enable_comments=enable_comments)
78 diff = diff_processor.as_html(enable_comments=enable_comments)
87 stats = diff_processor.stat()
79 stats = diff_processor.stat()
88 size = len(diff or '')
80 size = len(diff or '')
89 else:
81 else:
90 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
82 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
91 'diff menu to display this diff'))
83 'diff menu to display this diff'))
92 stats = (0, 0)
84 stats = (0, 0)
93 size = 0
85 size = 0
94 if not diff:
86 if not diff:
95 submodules = filter(lambda o: isinstance(o, SubModuleNode),
87 submodules = filter(lambda o: isinstance(o, SubModuleNode),
96 [filenode_new, filenode_old])
88 [filenode_new, filenode_old])
97 if submodules:
89 if submodules:
98 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
90 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
99 else:
91 else:
100 diff = wrap_to_table(_('No changes detected'))
92 diff = wrap_to_table(_('No changes detected'))
101
93
102 cs1 = filenode_old.changeset.raw_id
94 cs1 = filenode_old.changeset.raw_id
103 cs2 = filenode_new.changeset.raw_id
95 cs2 = filenode_new.changeset.raw_id
104
96
105 return size, cs1, cs2, diff, stats
97 return size, cs1, cs2, diff, stats
106
98
107
99
108 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
100 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
109 """
101 """
110 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
102 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
111
103
112 :param ignore_whitespace: ignore whitespaces in diff
104 :param ignore_whitespace: ignore whitespaces in diff
113 """
105 """
114 # make sure we pass in default context
106 # make sure we pass in default context
115 context = context or 3
107 context = context or 3
116 submodules = filter(lambda o: isinstance(o, SubModuleNode),
108 submodules = filter(lambda o: isinstance(o, SubModuleNode),
117 [filenode_new, filenode_old])
109 [filenode_new, filenode_old])
118 if submodules:
110 if submodules:
119 return ''
111 return ''
120
112
121 for filenode in (filenode_old, filenode_new):
113 for filenode in (filenode_old, filenode_new):
122 if not isinstance(filenode, FileNode):
114 if not isinstance(filenode, FileNode):
123 raise VCSError("Given object should be FileNode object, not %s"
115 raise VCSError("Given object should be FileNode object, not %s"
124 % filenode.__class__)
116 % filenode.__class__)
125
117
126 repo = filenode_new.changeset.repository
118 repo = filenode_new.changeset.repository
127 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
119 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
120 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
129
121
130 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
122 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
131 ignore_whitespace, context)
123 ignore_whitespace, context)
132 return vcs_gitdiff
124 return vcs_gitdiff
133
125
134 NEW_FILENODE = 1
126 NEW_FILENODE = 1
135 DEL_FILENODE = 2
127 DEL_FILENODE = 2
136 MOD_FILENODE = 3
128 MOD_FILENODE = 3
137 RENAMED_FILENODE = 4
129 RENAMED_FILENODE = 4
138 CHMOD_FILENODE = 5
130 CHMOD_FILENODE = 5
139
131
140
132
141 class DiffLimitExceeded(Exception):
133 class DiffLimitExceeded(Exception):
142 pass
134 pass
143
135
144
136
145 class LimitedDiffContainer(object):
137 class LimitedDiffContainer(object):
146
138
147 def __init__(self, diff_limit, cur_diff_size, diff):
139 def __init__(self, diff_limit, cur_diff_size, diff):
148 self.diff = diff
140 self.diff = diff
149 self.diff_limit = diff_limit
141 self.diff_limit = diff_limit
150 self.cur_diff_size = cur_diff_size
142 self.cur_diff_size = cur_diff_size
151
143
152 def __iter__(self):
144 def __iter__(self):
153 for l in self.diff:
145 for l in self.diff:
154 yield l
146 yield l
155
147
156
148
157 class DiffProcessor(object):
149 class DiffProcessor(object):
158 """
150 """
159 Give it a unified or git diff and it returns a list of the files that were
151 Give it a unified or git diff and it returns a list of the files that were
160 mentioned in the diff together with a dict of meta information that
152 mentioned in the diff together with a dict of meta information that
161 can be used to render it in a HTML template.
153 can be used to render it in a HTML template.
162 """
154 """
163 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
155 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
164 _newline_marker = re.compile(r'^\\ No newline at end of file')
156 _newline_marker = re.compile(r'^\\ No newline at end of file')
165 _git_header_re = re.compile(r"""
157 _git_header_re = re.compile(r"""
166 #^diff[ ]--git
158 #^diff[ ]--git
167 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
159 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
168 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
160 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
169 ^rename[ ]from[ ](?P<rename_from>\S+)\n
161 ^rename[ ]from[ ](?P<rename_from>\S+)\n
170 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
162 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
171 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
163 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
172 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
164 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
173 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
165 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
174 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
166 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
175 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
167 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
176 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
168 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
177 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
169 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
178 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
170 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
179 """, re.VERBOSE | re.MULTILINE)
171 """, re.VERBOSE | re.MULTILINE)
180 _hg_header_re = re.compile(r"""
172 _hg_header_re = re.compile(r"""
181 #^diff[ ]--git
173 #^diff[ ]--git
182 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
174 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
183 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
175 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
184 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
176 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
185 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
177 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
186 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
178 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
187 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
179 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
188 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
180 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
189 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
181 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
190 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
182 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
191 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
183 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
192 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
184 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
193 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
185 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
194 """, re.VERBOSE | re.MULTILINE)
186 """, re.VERBOSE | re.MULTILINE)
195
187
196 #used for inline highlighter word split
188 #used for inline highlighter word split
197 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
189 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
198
190
199 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
191 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
200 """
192 """
201 :param diff: a text in diff format
193 :param diff: a text in diff format
202 :param vcs: type of version controll hg or git
194 :param vcs: type of version controll hg or git
203 :param format: format of diff passed, `udiff` or `gitdiff`
195 :param format: format of diff passed, `udiff` or `gitdiff`
204 :param diff_limit: define the size of diff that is considered "big"
196 :param diff_limit: define the size of diff that is considered "big"
205 based on that parameter cut off will be triggered, set to None
197 based on that parameter cut off will be triggered, set to None
206 to show full diff
198 to show full diff
207 """
199 """
208 if not isinstance(diff, basestring):
200 if not isinstance(diff, basestring):
209 raise Exception('Diff must be a basestring got %s instead' % type(diff))
201 raise Exception('Diff must be a basestring got %s instead' % type(diff))
210
202
211 self._diff = diff
203 self._diff = diff
212 self._format = format
204 self._format = format
213 self.adds = 0
205 self.adds = 0
214 self.removes = 0
206 self.removes = 0
215 # calculate diff size
207 # calculate diff size
216 self.diff_size = len(diff)
208 self.diff_size = len(diff)
217 self.diff_limit = diff_limit
209 self.diff_limit = diff_limit
218 self.cur_diff_size = 0
210 self.cur_diff_size = 0
219 self.parsed = False
211 self.parsed = False
220 self.parsed_diff = []
212 self.parsed_diff = []
221 self.vcs = vcs
213 self.vcs = vcs
222
214
223 if format == 'gitdiff':
215 if format == 'gitdiff':
224 self.differ = self._highlight_line_difflib
216 self.differ = self._highlight_line_difflib
225 self._parser = self._parse_gitdiff
217 self._parser = self._parse_gitdiff
226 else:
218 else:
227 self.differ = self._highlight_line_udiff
219 self.differ = self._highlight_line_udiff
228 self._parser = self._parse_udiff
220 self._parser = self._parse_udiff
229
221
230 def _copy_iterator(self):
222 def _copy_iterator(self):
231 """
223 """
232 make a fresh copy of generator, we should not iterate thru
224 make a fresh copy of generator, we should not iterate thru
233 an original as it's needed for repeating operations on
225 an original as it's needed for repeating operations on
234 this instance of DiffProcessor
226 this instance of DiffProcessor
235 """
227 """
236 self.__udiff, iterator_copy = tee(self.__udiff)
228 self.__udiff, iterator_copy = tee(self.__udiff)
237 return iterator_copy
229 return iterator_copy
238
230
239 def _escaper(self, string):
231 def _escaper(self, string):
240 """
232 """
241 Escaper for diff escapes special chars and checks the diff limit
233 Escaper for diff escapes special chars and checks the diff limit
242
234
243 :param string:
235 :param string:
244 :type string:
236 :type string:
245 """
237 """
246
238
247 self.cur_diff_size += len(string)
239 self.cur_diff_size += len(string)
248
240
249 # escaper get's iterated on each .next() call and it checks if each
241 # escaper get's iterated on each .next() call and it checks if each
250 # parsed line doesn't exceed the diff limit
242 # parsed line doesn't exceed the diff limit
251 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
243 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
252 raise DiffLimitExceeded('Diff Limit Exceeded')
244 raise DiffLimitExceeded('Diff Limit Exceeded')
253
245
254 return safe_unicode(string).replace('&', '&amp;')\
246 return safe_unicode(string).replace('&', '&amp;')\
255 .replace('<', '&lt;')\
247 .replace('<', '&lt;')\
256 .replace('>', '&gt;')
248 .replace('>', '&gt;')
257
249
258 def _line_counter(self, l):
250 def _line_counter(self, l):
259 """
251 """
260 Checks each line and bumps total adds/removes for this diff
252 Checks each line and bumps total adds/removes for this diff
261
253
262 :param l:
254 :param l:
263 """
255 """
264 if l.startswith('+') and not l.startswith('+++'):
256 if l.startswith('+') and not l.startswith('+++'):
265 self.adds += 1
257 self.adds += 1
266 elif l.startswith('-') and not l.startswith('---'):
258 elif l.startswith('-') and not l.startswith('---'):
267 self.removes += 1
259 self.removes += 1
268 return safe_unicode(l)
260 return safe_unicode(l)
269
261
270 def _highlight_line_difflib(self, line, next_):
262 def _highlight_line_difflib(self, line, next_):
271 """
263 """
272 Highlight inline changes in both lines.
264 Highlight inline changes in both lines.
273 """
265 """
274
266
275 if line['action'] == 'del':
267 if line['action'] == 'del':
276 old, new = line, next_
268 old, new = line, next_
277 else:
269 else:
278 old, new = next_, line
270 old, new = next_, line
279
271
280 oldwords = self._token_re.split(old['line'])
272 oldwords = self._token_re.split(old['line'])
281 newwords = self._token_re.split(new['line'])
273 newwords = self._token_re.split(new['line'])
282 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
274 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
283
275
284 oldfragments, newfragments = [], []
276 oldfragments, newfragments = [], []
285 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
277 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
286 oldfrag = ''.join(oldwords[i1:i2])
278 oldfrag = ''.join(oldwords[i1:i2])
287 newfrag = ''.join(newwords[j1:j2])
279 newfrag = ''.join(newwords[j1:j2])
288 if tag != 'equal':
280 if tag != 'equal':
289 if oldfrag:
281 if oldfrag:
290 oldfrag = '<del>%s</del>' % oldfrag
282 oldfrag = '<del>%s</del>' % oldfrag
291 if newfrag:
283 if newfrag:
292 newfrag = '<ins>%s</ins>' % newfrag
284 newfrag = '<ins>%s</ins>' % newfrag
293 oldfragments.append(oldfrag)
285 oldfragments.append(oldfrag)
294 newfragments.append(newfrag)
286 newfragments.append(newfrag)
295
287
296 old['line'] = "".join(oldfragments)
288 old['line'] = "".join(oldfragments)
297 new['line'] = "".join(newfragments)
289 new['line'] = "".join(newfragments)
298
290
299 def _highlight_line_udiff(self, line, next_):
291 def _highlight_line_udiff(self, line, next_):
300 """
292 """
301 Highlight inline changes in both lines.
293 Highlight inline changes in both lines.
302 """
294 """
303 start = 0
295 start = 0
304 limit = min(len(line['line']), len(next_['line']))
296 limit = min(len(line['line']), len(next_['line']))
305 while start < limit and line['line'][start] == next_['line'][start]:
297 while start < limit and line['line'][start] == next_['line'][start]:
306 start += 1
298 start += 1
307 end = -1
299 end = -1
308 limit -= start
300 limit -= start
309 while -end <= limit and line['line'][end] == next_['line'][end]:
301 while -end <= limit and line['line'][end] == next_['line'][end]:
310 end -= 1
302 end -= 1
311 end += 1
303 end += 1
312 if start or end:
304 if start or end:
313 def do(l):
305 def do(l):
314 last = end + len(l['line'])
306 last = end + len(l['line'])
315 if l['action'] == 'add':
307 if l['action'] == 'add':
316 tag = 'ins'
308 tag = 'ins'
317 else:
309 else:
318 tag = 'del'
310 tag = 'del'
319 l['line'] = '%s<%s>%s</%s>%s' % (
311 l['line'] = '%s<%s>%s</%s>%s' % (
320 l['line'][:start],
312 l['line'][:start],
321 tag,
313 tag,
322 l['line'][start:last],
314 l['line'][start:last],
323 tag,
315 tag,
324 l['line'][last:]
316 l['line'][last:]
325 )
317 )
326 do(line)
318 do(line)
327 do(next_)
319 do(next_)
328
320
329 def _get_header(self, diff_chunk):
321 def _get_header(self, diff_chunk):
330 """
322 """
331 parses the diff header, and returns parts, and leftover diff
323 parses the diff header, and returns parts, and leftover diff
332 parts consists of 14 elements::
324 parts consists of 14 elements::
333
325
334 a_path, b_path, similarity_index, rename_from, rename_to,
326 a_path, b_path, similarity_index, rename_from, rename_to,
335 old_mode, new_mode, new_file_mode, deleted_file_mode,
327 old_mode, new_mode, new_file_mode, deleted_file_mode,
336 a_blob_id, b_blob_id, b_mode, a_file, b_file
328 a_blob_id, b_blob_id, b_mode, a_file, b_file
337
329
338 :param diff_chunk:
330 :param diff_chunk:
339 :type diff_chunk:
331 :type diff_chunk:
340 """
332 """
341
333
342 if self.vcs == 'git':
334 if self.vcs == 'git':
343 match = self._git_header_re.match(diff_chunk)
335 match = self._git_header_re.match(diff_chunk)
344 diff = diff_chunk[match.end():]
336 diff = diff_chunk[match.end():]
345 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
337 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
346 elif self.vcs == 'hg':
338 elif self.vcs == 'hg':
347 match = self._hg_header_re.match(diff_chunk)
339 match = self._hg_header_re.match(diff_chunk)
348 diff = diff_chunk[match.end():]
340 diff = diff_chunk[match.end():]
349 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
341 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
350 else:
342 else:
351 raise Exception('VCS type %s is not supported' % self.vcs)
343 raise Exception('VCS type %s is not supported' % self.vcs)
352
344
353 def _clean_line(self, line, command):
345 def _clean_line(self, line, command):
354 if command in ['+', '-', ' ']:
346 if command in ['+', '-', ' ']:
355 #only modify the line if it's actually a diff thing
347 #only modify the line if it's actually a diff thing
356 line = line[1:]
348 line = line[1:]
357 return line
349 return line
358
350
359 def _parse_gitdiff(self, inline_diff=True):
351 def _parse_gitdiff(self, inline_diff=True):
360 _files = []
352 _files = []
361 diff_container = lambda arg: arg
353 diff_container = lambda arg: arg
362
354
363 ##split the diff in chunks of separate --git a/file b/file chunks
355 ##split the diff in chunks of separate --git a/file b/file chunks
364 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
365 binary = False
357 binary = False
366 binary_msg = 'unknown binary'
358 binary_msg = 'unknown binary'
367 head, diff = self._get_header(raw_diff)
359 head, diff = self._get_header(raw_diff)
368
360
369 if not head['a_file'] and head['b_file']:
361 if not head['a_file'] and head['b_file']:
370 op = 'A'
362 op = 'A'
371 elif head['a_file'] and head['b_file']:
363 elif head['a_file'] and head['b_file']:
372 op = 'M'
364 op = 'M'
373 elif head['a_file'] and not head['b_file']:
365 elif head['a_file'] and not head['b_file']:
374 op = 'D'
366 op = 'D'
375 else:
367 else:
376 #probably we're dealing with a binary file 1
368 #probably we're dealing with a binary file 1
377 binary = True
369 binary = True
378 if head['deleted_file_mode']:
370 if head['deleted_file_mode']:
379 op = 'D'
371 op = 'D'
380 stats = ['b', DEL_FILENODE]
372 stats = ['b', DEL_FILENODE]
381 binary_msg = 'deleted binary file'
373 binary_msg = 'deleted binary file'
382 elif head['new_file_mode']:
374 elif head['new_file_mode']:
383 op = 'A'
375 op = 'A'
384 stats = ['b', NEW_FILENODE]
376 stats = ['b', NEW_FILENODE]
385 binary_msg = 'new binary file %s' % head['new_file_mode']
377 binary_msg = 'new binary file %s' % head['new_file_mode']
386 else:
378 else:
387 if head['new_mode'] and head['old_mode']:
379 if head['new_mode'] and head['old_mode']:
388 stats = ['b', CHMOD_FILENODE]
380 stats = ['b', CHMOD_FILENODE]
389 op = 'M'
381 op = 'M'
390 binary_msg = ('modified binary file chmod %s => %s'
382 binary_msg = ('modified binary file chmod %s => %s'
391 % (head['old_mode'], head['new_mode']))
383 % (head['old_mode'], head['new_mode']))
392 elif (head['rename_from'] and head['rename_to']
384 elif (head['rename_from'] and head['rename_to']
393 and head['rename_from'] != head['rename_to']):
385 and head['rename_from'] != head['rename_to']):
394 stats = ['b', RENAMED_FILENODE]
386 stats = ['b', RENAMED_FILENODE]
395 op = 'M'
387 op = 'M'
396 binary_msg = ('file renamed from %s to %s'
388 binary_msg = ('file renamed from %s to %s'
397 % (head['rename_from'], head['rename_to']))
389 % (head['rename_from'], head['rename_to']))
398 else:
390 else:
399 stats = ['b', MOD_FILENODE]
391 stats = ['b', MOD_FILENODE]
400 op = 'M'
392 op = 'M'
401 binary_msg = 'modified binary file'
393 binary_msg = 'modified binary file'
402
394
403 if not binary:
395 if not binary:
404 try:
396 try:
405 chunks, stats = self._parse_lines(diff)
397 chunks, stats = self._parse_lines(diff)
406 except DiffLimitExceeded:
398 except DiffLimitExceeded:
407 diff_container = lambda _diff: LimitedDiffContainer(
399 diff_container = lambda _diff: LimitedDiffContainer(
408 self.diff_limit,
400 self.diff_limit,
409 self.cur_diff_size,
401 self.cur_diff_size,
410 _diff)
402 _diff)
411 break
403 break
412 else:
404 else:
413 chunks = []
405 chunks = []
414 chunks.append([{
406 chunks.append([{
415 'old_lineno': '',
407 'old_lineno': '',
416 'new_lineno': '',
408 'new_lineno': '',
417 'action': 'binary',
409 'action': 'binary',
418 'line': binary_msg,
410 'line': binary_msg,
419 }])
411 }])
420
412
421 _files.append({
413 _files.append({
422 'filename': head['b_path'],
414 'filename': head['b_path'],
423 'old_revision': head['a_blob_id'],
415 'old_revision': head['a_blob_id'],
424 'new_revision': head['b_blob_id'],
416 'new_revision': head['b_blob_id'],
425 'chunks': chunks,
417 'chunks': chunks,
426 'operation': op,
418 'operation': op,
427 'stats': stats,
419 'stats': stats,
428 })
420 })
429
421
430 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
431
423
432 if inline_diff is False:
424 if inline_diff is False:
433 return diff_container(sorted(_files, key=sorter))
425 return diff_container(sorted(_files, key=sorter))
434
426
435 # highlight inline changes
427 # highlight inline changes
436 for diff_data in _files:
428 for diff_data in _files:
437 for chunk in diff_data['chunks']:
429 for chunk in diff_data['chunks']:
438 lineiter = iter(chunk)
430 lineiter = iter(chunk)
439 try:
431 try:
440 while 1:
432 while 1:
441 line = lineiter.next()
433 line = lineiter.next()
442 if line['action'] not in ['unmod', 'context']:
434 if line['action'] not in ['unmod', 'context']:
443 nextline = lineiter.next()
435 nextline = lineiter.next()
444 if nextline['action'] in ['unmod', 'context'] or \
436 if nextline['action'] in ['unmod', 'context'] or \
445 nextline['action'] == line['action']:
437 nextline['action'] == line['action']:
446 continue
438 continue
447 self.differ(line, nextline)
439 self.differ(line, nextline)
448 except StopIteration:
440 except StopIteration:
449 pass
441 pass
450
442
451 return diff_container(sorted(_files, key=sorter))
443 return diff_container(sorted(_files, key=sorter))
452
444
453 def _parse_udiff(self, inline_diff=True):
445 def _parse_udiff(self, inline_diff=True):
454 raise NotImplementedError()
446 raise NotImplementedError()
455
447
456 def _parse_lines(self, diff):
448 def _parse_lines(self, diff):
457 """
449 """
458 Parse the diff an return data for the template.
450 Parse the diff an return data for the template.
459 """
451 """
460
452
461 lineiter = iter(diff)
453 lineiter = iter(diff)
462 stats = [0, 0]
454 stats = [0, 0]
463
455
464 try:
456 try:
465 chunks = []
457 chunks = []
466 line = lineiter.next()
458 line = lineiter.next()
467
459
468 while line:
460 while line:
469 lines = []
461 lines = []
470 chunks.append(lines)
462 chunks.append(lines)
471
463
472 match = self._chunk_re.match(line)
464 match = self._chunk_re.match(line)
473
465
474 if not match:
466 if not match:
475 break
467 break
476
468
477 gr = match.groups()
469 gr = match.groups()
478 (old_line, old_end,
470 (old_line, old_end,
479 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
480 old_line -= 1
472 old_line -= 1
481 new_line -= 1
473 new_line -= 1
482
474
483 context = len(gr) == 5
475 context = len(gr) == 5
484 old_end += old_line
476 old_end += old_line
485 new_end += new_line
477 new_end += new_line
486
478
487 if context:
479 if context:
488 # skip context only if it's first line
480 # skip context only if it's first line
489 if int(gr[0]) > 1:
481 if int(gr[0]) > 1:
490 lines.append({
482 lines.append({
491 'old_lineno': '...',
483 'old_lineno': '...',
492 'new_lineno': '...',
484 'new_lineno': '...',
493 'action': 'context',
485 'action': 'context',
494 'line': line,
486 'line': line,
495 })
487 })
496
488
497 line = lineiter.next()
489 line = lineiter.next()
498
490
499 while old_line < old_end or new_line < new_end:
491 while old_line < old_end or new_line < new_end:
500 command = ' '
492 command = ' '
501 if line:
493 if line:
502 command = line[0]
494 command = line[0]
503
495
504 affects_old = affects_new = False
496 affects_old = affects_new = False
505
497
506 # ignore those if we don't expect them
498 # ignore those if we don't expect them
507 if command in '#@':
499 if command in '#@':
508 continue
500 continue
509 elif command == '+':
501 elif command == '+':
510 affects_new = True
502 affects_new = True
511 action = 'add'
503 action = 'add'
512 stats[0] += 1
504 stats[0] += 1
513 elif command == '-':
505 elif command == '-':
514 affects_old = True
506 affects_old = True
515 action = 'del'
507 action = 'del'
516 stats[1] += 1
508 stats[1] += 1
517 else:
509 else:
518 affects_old = affects_new = True
510 affects_old = affects_new = True
519 action = 'unmod'
511 action = 'unmod'
520
512
521 if not self._newline_marker.match(line):
513 if not self._newline_marker.match(line):
522 old_line += affects_old
514 old_line += affects_old
523 new_line += affects_new
515 new_line += affects_new
524 lines.append({
516 lines.append({
525 'old_lineno': affects_old and old_line or '',
517 'old_lineno': affects_old and old_line or '',
526 'new_lineno': affects_new and new_line or '',
518 'new_lineno': affects_new and new_line or '',
527 'action': action,
519 'action': action,
528 'line': self._clean_line(line, command)
520 'line': self._clean_line(line, command)
529 })
521 })
530
522
531 line = lineiter.next()
523 line = lineiter.next()
532
524
533 if self._newline_marker.match(line):
525 if self._newline_marker.match(line):
534 # we need to append to lines, since this is not
526 # we need to append to lines, since this is not
535 # counted in the line specs of diff
527 # counted in the line specs of diff
536 lines.append({
528 lines.append({
537 'old_lineno': '...',
529 'old_lineno': '...',
538 'new_lineno': '...',
530 'new_lineno': '...',
539 'action': 'context',
531 'action': 'context',
540 'line': self._clean_line(line, command)
532 'line': self._clean_line(line, command)
541 })
533 })
542
534
543 except StopIteration:
535 except StopIteration:
544 pass
536 pass
545 return chunks, stats
537 return chunks, stats
546
538
547 def _safe_id(self, idstring):
539 def _safe_id(self, idstring):
548 """Make a string safe for including in an id attribute.
540 """Make a string safe for including in an id attribute.
549
541
550 The HTML spec says that id attributes 'must begin with
542 The HTML spec says that id attributes 'must begin with
551 a letter ([A-Za-z]) and may be followed by any number
543 a letter ([A-Za-z]) and may be followed by any number
552 of letters, digits ([0-9]), hyphens ("-"), underscores
544 of letters, digits ([0-9]), hyphens ("-"), underscores
553 ("_"), colons (":"), and periods (".")'. These regexps
545 ("_"), colons (":"), and periods (".")'. These regexps
554 are slightly over-zealous, in that they remove colons
546 are slightly over-zealous, in that they remove colons
555 and periods unnecessarily.
547 and periods unnecessarily.
556
548
557 Whitespace is transformed into underscores, and then
549 Whitespace is transformed into underscores, and then
558 anything which is not a hyphen or a character that
550 anything which is not a hyphen or a character that
559 matches \w (alphanumerics and underscore) is removed.
551 matches \w (alphanumerics and underscore) is removed.
560
552
561 """
553 """
562 # Transform all whitespace to underscore
554 # Transform all whitespace to underscore
563 idstring = re.sub(r'\s', "_", '%s' % idstring)
555 idstring = re.sub(r'\s', "_", '%s' % idstring)
564 # Remove everything that is not a hyphen or a member of \w
556 # Remove everything that is not a hyphen or a member of \w
565 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
557 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
566 return idstring
558 return idstring
567
559
568 def prepare(self, inline_diff=True):
560 def prepare(self, inline_diff=True):
569 """
561 """
570 Prepare the passed udiff for HTML rendering. It'l return a list
562 Prepare the passed udiff for HTML rendering. It'l return a list
571 of dicts with diff information
563 of dicts with diff information
572 """
564 """
573 parsed = self._parser(inline_diff=inline_diff)
565 parsed = self._parser(inline_diff=inline_diff)
574 self.parsed = True
566 self.parsed = True
575 self.parsed_diff = parsed
567 self.parsed_diff = parsed
576 return parsed
568 return parsed
577
569
578 def as_raw(self, diff_lines=None):
570 def as_raw(self, diff_lines=None):
579 """
571 """
580 Returns raw string diff
572 Returns raw string diff
581 """
573 """
582 return self._diff
574 return self._diff
583 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
575 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
584
576
585 def as_html(self, table_class='code-difftable', line_class='line',
577 def as_html(self, table_class='code-difftable', line_class='line',
586 old_lineno_class='lineno old', new_lineno_class='lineno new',
578 old_lineno_class='lineno old', new_lineno_class='lineno new',
587 code_class='code', enable_comments=False, parsed_lines=None):
579 code_class='code', enable_comments=False, parsed_lines=None):
588 """
580 """
589 Return given diff as html table with customized css classes
581 Return given diff as html table with customized css classes
590 """
582 """
591 def _link_to_if(condition, label, url):
583 def _link_to_if(condition, label, url):
592 """
584 """
593 Generates a link if condition is meet or just the label if not.
585 Generates a link if condition is meet or just the label if not.
594 """
586 """
595
587
596 if condition:
588 if condition:
597 return '''<a href="%(url)s">%(label)s</a>''' % {
589 return '''<a href="%(url)s">%(label)s</a>''' % {
598 'url': url,
590 'url': url,
599 'label': label
591 'label': label
600 }
592 }
601 else:
593 else:
602 return label
594 return label
603 if not self.parsed:
595 if not self.parsed:
604 self.prepare()
596 self.prepare()
605
597
606 diff_lines = self.parsed_diff
598 diff_lines = self.parsed_diff
607 if parsed_lines:
599 if parsed_lines:
608 diff_lines = parsed_lines
600 diff_lines = parsed_lines
609
601
610 _html_empty = True
602 _html_empty = True
611 _html = []
603 _html = []
612 _html.append('''<table class="%(table_class)s">\n''' % {
604 _html.append('''<table class="%(table_class)s">\n''' % {
613 'table_class': table_class
605 'table_class': table_class
614 })
606 })
615
607
616 for diff in diff_lines:
608 for diff in diff_lines:
617 for line in diff['chunks']:
609 for line in diff['chunks']:
618 _html_empty = False
610 _html_empty = False
619 for change in line:
611 for change in line:
620 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
612 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
621 'lc': line_class,
613 'lc': line_class,
622 'action': change['action']
614 'action': change['action']
623 })
615 })
624 anchor_old_id = ''
616 anchor_old_id = ''
625 anchor_new_id = ''
617 anchor_new_id = ''
626 anchor_old = "%(filename)s_o%(oldline_no)s" % {
618 anchor_old = "%(filename)s_o%(oldline_no)s" % {
627 'filename': self._safe_id(diff['filename']),
619 'filename': self._safe_id(diff['filename']),
628 'oldline_no': change['old_lineno']
620 'oldline_no': change['old_lineno']
629 }
621 }
630 anchor_new = "%(filename)s_n%(oldline_no)s" % {
622 anchor_new = "%(filename)s_n%(oldline_no)s" % {
631 'filename': self._safe_id(diff['filename']),
623 'filename': self._safe_id(diff['filename']),
632 'oldline_no': change['new_lineno']
624 'oldline_no': change['new_lineno']
633 }
625 }
634 cond_old = (change['old_lineno'] != '...' and
626 cond_old = (change['old_lineno'] != '...' and
635 change['old_lineno'])
627 change['old_lineno'])
636 cond_new = (change['new_lineno'] != '...' and
628 cond_new = (change['new_lineno'] != '...' and
637 change['new_lineno'])
629 change['new_lineno'])
638 if cond_old:
630 if cond_old:
639 anchor_old_id = 'id="%s"' % anchor_old
631 anchor_old_id = 'id="%s"' % anchor_old
640 if cond_new:
632 if cond_new:
641 anchor_new_id = 'id="%s"' % anchor_new
633 anchor_new_id = 'id="%s"' % anchor_new
642 ###########################################################
634 ###########################################################
643 # OLD LINE NUMBER
635 # OLD LINE NUMBER
644 ###########################################################
636 ###########################################################
645 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
637 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
646 'a_id': anchor_old_id,
638 'a_id': anchor_old_id,
647 'olc': old_lineno_class
639 'olc': old_lineno_class
648 })
640 })
649
641
650 _html.append('''%(link)s''' % {
642 _html.append('''%(link)s''' % {
651 'link': _link_to_if(True, change['old_lineno'],
643 'link': _link_to_if(True, change['old_lineno'],
652 '#%s' % anchor_old)
644 '#%s' % anchor_old)
653 })
645 })
654 _html.append('''</td>\n''')
646 _html.append('''</td>\n''')
655 ###########################################################
647 ###########################################################
656 # NEW LINE NUMBER
648 # NEW LINE NUMBER
657 ###########################################################
649 ###########################################################
658
650
659 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
651 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
660 'a_id': anchor_new_id,
652 'a_id': anchor_new_id,
661 'nlc': new_lineno_class
653 'nlc': new_lineno_class
662 })
654 })
663
655
664 _html.append('''%(link)s''' % {
656 _html.append('''%(link)s''' % {
665 'link': _link_to_if(True, change['new_lineno'],
657 'link': _link_to_if(True, change['new_lineno'],
666 '#%s' % anchor_new)
658 '#%s' % anchor_new)
667 })
659 })
668 _html.append('''</td>\n''')
660 _html.append('''</td>\n''')
669 ###########################################################
661 ###########################################################
670 # CODE
662 # CODE
671 ###########################################################
663 ###########################################################
672 comments = '' if enable_comments else 'no-comment'
664 comments = '' if enable_comments else 'no-comment'
673 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
665 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
674 'cc': code_class,
666 'cc': code_class,
675 'inc': comments
667 'inc': comments
676 })
668 })
677 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
669 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
678 'code': change['line']
670 'code': change['line']
679 })
671 })
680
672
681 _html.append('''\t</td>''')
673 _html.append('''\t</td>''')
682 _html.append('''\n</tr>\n''')
674 _html.append('''\n</tr>\n''')
683 _html.append('''</table>''')
675 _html.append('''</table>''')
684 if _html_empty:
676 if _html_empty:
685 return None
677 return None
686 return ''.join(_html)
678 return ''.join(_html)
687
679
688 def stat(self):
680 def stat(self):
689 """
681 """
690 Returns tuple of added, and removed lines for this instance
682 Returns tuple of added, and removed lines for this instance
691 """
683 """
692 return self.adds, self.removes
684 return self.adds, self.removes
693
685
694
686
695 class InMemoryBundleRepo(bundlerepository):
696 def __init__(self, ui, path, bundlestream):
697 self._tempparent = None
698 localrepo.localrepository.__init__(self, ui, path)
699 self.ui.setconfig('phases', 'publish', False)
700
701 self.bundle = bundlestream
702
703 # dict with the mapping 'filename' -> position in the bundle
704 self.bundlefilespos = {}
705
706
707 def differ(org_repo, org_ref, other_repo, other_ref,
687 def differ(org_repo, org_ref, other_repo, other_ref,
708 remote_compare=False, context=3, ignore_whitespace=False):
688 context=3, ignore_whitespace=False):
709 """
689 """
710 General differ between branches, bookmarks, revisions of two remote or
690 General differ between branches, bookmarks, revisions of two remote or
711 local but related repositories
691 local but related repositories
712
692
713 :param org_repo:
693 :param org_repo:
714 :param org_ref:
694 :param org_ref:
715 :param other_repo:
695 :param other_repo:
716 :type other_repo:
696 :type other_repo:
717 :type other_ref:
697 :type other_ref:
718 """
698 """
719
699
720 org_repo_scm = org_repo.scm_instance
700 org_repo_scm = org_repo.scm_instance
721 other_repo_scm = other_repo.scm_instance
701 other_repo_scm = other_repo.scm_instance
722
702
723 org_repo = org_repo_scm._repo
703 org_repo = org_repo_scm._repo
724 other_repo = other_repo_scm._repo
704 other_repo = other_repo_scm._repo
725
705
726 org_ref = org_ref[1]
706 org_ref = org_ref[1]
727 other_ref = other_ref[1]
707 other_ref = other_ref[1]
728
708
729 if org_repo_scm == other_repo_scm:
709 if org_repo_scm == other_repo_scm:
730 log.debug('running diff between %s@%s and %s@%s'
710 log.debug('running diff between %s@%s and %s@%s'
731 % (org_repo.path, org_ref, other_repo.path, other_ref))
711 % (org_repo.path, org_ref, other_repo.path, other_ref))
732 _diff = org_repo_scm.get_diff(rev1=org_ref, rev2=other_ref,
712 _diff = org_repo_scm.get_diff(rev1=org_ref, rev2=other_ref,
733 ignore_whitespace=ignore_whitespace, context=context)
713 ignore_whitespace=ignore_whitespace, context=context)
734 return _diff
714 return _diff
735
715
736 elif remote_compare:
737 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
738 org_repo_peer = localrepo.locallegacypeer(org_repo.local())
739 # create a bundle (uncompressed if other repo is not local)
740 if org_repo_peer.capable('getbundle'):
741 # disable repo hooks here since it's just bundle !
742 # patch and reset hooks section of UI config to not run any
743 # hooks on fetching archives with subrepos
744 for k, _ in org_repo.ui.configitems('hooks'):
745 org_repo.ui.setconfig('hooks', k, None)
746 unbundle = org_repo.getbundle('incoming', common=None,
747 heads=None)
748
749 buf = BytesIO()
750 while True:
751 chunk = unbundle._stream.read(1024 * 4)
752 if not chunk:
753 break
754 buf.write(chunk)
755
756 buf.seek(0)
757 # replace chunked _stream with data that can do tell() and seek()
758 unbundle._stream = buf
759
760 ui = make_ui('db')
761 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
762 bundlestream=unbundle)
763
764 return ''.join(patch.diff(bundlerepo,
765 node1=other_repo[other_ref].node(),
766 node2=org_repo[org_ref].node(),
767 opts=opts))
768
769 return ''
716 return ''
@@ -1,204 +1,204 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('New pull request')}
12 ${_('New pull request')}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 <div style="float:left;padding:0px 30px 30px 30px">
23 <div style="float:left;padding:0px 30px 30px 30px">
24 <input type="hidden" name="rev_start" value="${request.GET.get('rev_start')}" />
24 <input type="hidden" name="rev_start" value="${request.GET.get('rev_start')}" />
25 <input type="hidden" name="rev_end" value="${request.GET.get('rev_end')}" />
25 <input type="hidden" name="rev_end" value="${request.GET.get('rev_end')}" />
26
26
27 ##ORG
27 ##ORG
28 <div style="float:left">
28 <div style="float:left">
29 <div class="fork_user">
29 <div class="fork_user">
30 <div class="gravatar">
30 <div class="gravatar">
31 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
31 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
32 </div>
32 </div>
33 <span style="font-size: 20px">
33 <span style="font-size: 20px">
34 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
34 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
35 </span>
35 </span>
36 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
36 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
37 </div>
37 </div>
38 <div style="clear:both;padding-top: 10px"></div>
38 <div style="clear:both;padding-top: 10px"></div>
39 </div>
39 </div>
40 <div style="float:left;font-size:24px;padding:0px 20px">
40 <div style="float:left;font-size:24px;padding:0px 20px">
41 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
41 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
42 </div>
42 </div>
43
43
44 ##OTHER, most Probably the PARENT OF THIS FORK
44 ##OTHER, most Probably the PARENT OF THIS FORK
45 <div style="float:left">
45 <div style="float:left">
46 <div class="fork_user">
46 <div class="fork_user">
47 <div class="gravatar">
47 <div class="gravatar">
48 <img id="other_repo_gravatar" alt="gravatar" src=""/>
48 <img id="other_repo_gravatar" alt="gravatar" src=""/>
49 </div>
49 </div>
50 <span style="font-size: 20px">
50 <span style="font-size: 20px">
51 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_pull_request_rev,c.default_revs,class_='refs')}
51 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_pull_request_rev,c.default_revs,class_='refs')}
52 </span>
52 </span>
53 <span style="padding:3px">
53 <span style="padding:3px">
54 <a id="refresh" href="#" class="tooltip" title="${h.tooltip(_('refresh overview'))}">
54 <a id="refresh" href="#" class="tooltip" title="${h.tooltip(_('refresh overview'))}">
55 <img style="margin:3px" class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
55 <img style="margin:3px" class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
56 </a>
56 </a>
57 </span>
57 </span>
58 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
58 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
59 </div>
59 </div>
60 <div style="clear:both;padding-top: 10px"></div>
60 <div style="clear:both;padding-top: 10px"></div>
61 </div>
61 </div>
62 <div style="clear:both;padding-top: 10px"></div>
62 <div style="clear:both;padding-top: 10px"></div>
63 ## overview pulled by ajax
63 ## overview pulled by ajax
64 <div style="float:left" id="pull_request_overview"></div>
64 <div style="float:left" id="pull_request_overview"></div>
65 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
65 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
66 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
66 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
67 </div>
67 </div>
68 </div>
68 </div>
69 <div style="float:left; border-left:1px dashed #eee">
69 <div style="float:left; border-left:1px dashed #eee">
70 <h4>${_('Pull request reviewers')}</h4>
70 <h4>${_('Pull request reviewers')}</h4>
71 <div id="reviewers" style="padding:0px 0px 0px 15px">
71 <div id="reviewers" style="padding:0px 0px 0px 15px">
72 ## members goes here !
72 ## members goes here !
73 <div class="group_members_wrap">
73 <div class="group_members_wrap">
74 <ul id="review_members" class="group_members">
74 <ul id="review_members" class="group_members">
75 %for member in c.review_members:
75 %for member in c.review_members:
76 <li id="reviewer_${member.user_id}">
76 <li id="reviewer_${member.user_id}">
77 <div class="reviewers_member">
77 <div class="reviewers_member">
78 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
78 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
79 <div style="float:left">${member.full_name} (${_('owner')})</div>
79 <div style="float:left">${member.full_name} (${_('owner')})</div>
80 <input type="hidden" value="${member.user_id}" name="review_members" />
80 <input type="hidden" value="${member.user_id}" name="review_members" />
81 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
81 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
82 </div>
82 </div>
83 </li>
83 </li>
84 %endfor
84 %endfor
85 </ul>
85 </ul>
86 </div>
86 </div>
87
87
88 <div class='ac'>
88 <div class='ac'>
89 <div class="reviewer_ac">
89 <div class="reviewer_ac">
90 ${h.text('user', class_='yui-ac-input')}
90 ${h.text('user', class_='yui-ac-input')}
91 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
91 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
92 <div id="reviewers_container"></div>
92 <div id="reviewers_container"></div>
93 </div>
93 </div>
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97 <h3>${_('Create new pull request')}</h3>
97 <h3>${_('Create new pull request')}</h3>
98
98
99 <div class="form">
99 <div class="form">
100 <!-- fields -->
100 <!-- fields -->
101
101
102 <div class="fields">
102 <div class="fields">
103
103
104 <div class="field">
104 <div class="field">
105 <div class="label">
105 <div class="label">
106 <label for="pullrequest_title">${_('Title')}:</label>
106 <label for="pullrequest_title">${_('Title')}:</label>
107 </div>
107 </div>
108 <div class="input">
108 <div class="input">
109 ${h.text('pullrequest_title',size=30)}
109 ${h.text('pullrequest_title',size=30)}
110 </div>
110 </div>
111 </div>
111 </div>
112
112
113 <div class="field">
113 <div class="field">
114 <div class="label label-textarea">
114 <div class="label label-textarea">
115 <label for="pullrequest_desc">${_('description')}:</label>
115 <label for="pullrequest_desc">${_('description')}:</label>
116 </div>
116 </div>
117 <div class="textarea text-area editor">
117 <div class="textarea text-area editor">
118 ${h.textarea('pullrequest_desc',size=30)}
118 ${h.textarea('pullrequest_desc',size=30)}
119 </div>
119 </div>
120 </div>
120 </div>
121
121
122 <div class="buttons">
122 <div class="buttons">
123 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
123 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
124 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
124 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
125 </div>
125 </div>
126 </div>
126 </div>
127 </div>
127 </div>
128 ${h.end_form()}
128 ${h.end_form()}
129
129
130 </div>
130 </div>
131
131
132 <script type="text/javascript">
132 <script type="text/javascript">
133 var _USERS_AC_DATA = ${c.users_array|n};
133 var _USERS_AC_DATA = ${c.users_array|n};
134 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
134 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
135 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
135 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
136
136
137 var other_repos_info = ${c.other_repos_info|n};
137 var other_repos_info = ${c.other_repos_info|n};
138
138
139 var loadPreview = function(){
139 var loadPreview = function(){
140 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
140 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
141 var url = "${h.url('compare_url',
141 var url = "${h.url('compare_url',
142 repo_name='org_repo',
142 repo_name='org_repo',
143 org_ref_type='org_ref_type', org_ref='org_ref',
143 org_ref_type='org_ref_type', org_ref='org_ref',
144 other_ref_type='other_ref_type', other_ref='other_ref',
144 other_ref_type='other_ref_type', other_ref='other_ref',
145 repo='other_repo',
145 repo='other_repo',
146 as_form=True, bundle=False,
146 as_form=True,
147 rev_start=request.GET.get('rev_start',''),
147 rev_start=request.GET.get('rev_start',''),
148 rev_end=request.GET.get('rev_end',''))}";
148 rev_end=request.GET.get('rev_end',''))}";
149
149
150 var select_refs = YUQ('#pull_request_form select.refs')
150 var select_refs = YUQ('#pull_request_form select.refs')
151 var rev_data = {}; // gather the org/other ref and repo here
151 var rev_data = {}; // gather the org/other ref and repo here
152 for(var i=0;i<select_refs.length;i++){
152 for(var i=0;i<select_refs.length;i++){
153 var select_ref = select_refs[i];
153 var select_ref = select_refs[i];
154 var select_ref_data = select_ref.value.split(':');
154 var select_ref_data = select_ref.value.split(':');
155 var key = null;
155 var key = null;
156 var val = null;
156 var val = null;
157
157
158 if(select_ref_data.length>1){
158 if(select_ref_data.length>1){
159 key = select_ref.name+"_type";
159 key = select_ref.name+"_type";
160 val = select_ref_data[0];
160 val = select_ref_data[0];
161 url = url.replace(key,val);
161 url = url.replace(key,val);
162 rev_data[key] = val;
162 rev_data[key] = val;
163
163
164 key = select_ref.name;
164 key = select_ref.name;
165 val = select_ref_data[1];
165 val = select_ref_data[1];
166 url = url.replace(key,val);
166 url = url.replace(key,val);
167 rev_data[key] = val;
167 rev_data[key] = val;
168
168
169 }else{
169 }else{
170 key = select_ref.name;
170 key = select_ref.name;
171 val = select_ref.value;
171 val = select_ref.value;
172 url = url.replace(key,val);
172 url = url.replace(key,val);
173 rev_data[key] = val;
173 rev_data[key] = val;
174 }
174 }
175 }
175 }
176
176
177 YUE.on('other_repo', 'change', function(e){
177 YUE.on('other_repo', 'change', function(e){
178 var repo_name = e.currentTarget.value;
178 var repo_name = e.currentTarget.value;
179 // replace the <select> of changed repo
179 // replace the <select> of changed repo
180 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
180 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
181 });
181 });
182
182
183 ypjax(url,'pull_request_overview', function(data){
183 ypjax(url,'pull_request_overview', function(data){
184 var sel_box = YUQ('#pull_request_form #other_repo')[0];
184 var sel_box = YUQ('#pull_request_form #other_repo')[0];
185 var repo_name = sel_box.options[sel_box.selectedIndex].value;
185 var repo_name = sel_box.options[sel_box.selectedIndex].value;
186 YUD.get('pull_request_overview_url').href = url;
186 YUD.get('pull_request_overview_url').href = url;
187 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
187 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
188 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
188 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
189 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
189 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
190 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
190 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
191 // select back the revision that was just compared
191 // select back the revision that was just compared
192 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
192 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
193 })
193 })
194 }
194 }
195 YUE.on('refresh','click',function(e){
195 YUE.on('refresh','click',function(e){
196 loadPreview()
196 loadPreview()
197 })
197 })
198
198
199 //lazy load overview after 0.5s
199 //lazy load overview after 0.5s
200 setTimeout(loadPreview, 500)
200 setTimeout(loadPreview, 500)
201
201
202 </script>
202 </script>
203
203
204 </%def>
204 </%def>
@@ -1,153 +1,152 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.repo import RepoModel
2 from rhodecode.model.repo import RepoModel
3 from rhodecode.model.meta import Session
3 from rhodecode.model.meta import Session
4 from rhodecode.model.db import Repository
4 from rhodecode.model.db import Repository
5 from rhodecode.model.scm import ScmModel
5 from rhodecode.model.scm import ScmModel
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7
7
8
8
9 class TestCompareController(TestController):
9 class TestCompareController(TestController):
10
10
11 def test_compare_tag_hg(self):
11 def test_compare_tag_hg(self):
12 self.log_user()
12 self.log_user()
13 tag1 = '0.1.2'
13 tag1 = '0.1.2'
14 tag2 = '0.1.3'
14 tag2 = '0.1.3'
15 response = self.app.get(url(controller='compare', action='index',
15 response = self.app.get(url(controller='compare', action='index',
16 repo_name=HG_REPO,
16 repo_name=HG_REPO,
17 org_ref_type="tag",
17 org_ref_type="tag",
18 org_ref=tag1,
18 org_ref=tag1,
19 other_ref_type="tag",
19 other_ref_type="tag",
20 other_ref=tag2,
20 other_ref=tag2,
21 ))
21 ))
22 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
22 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
23 ## outgoing changesets between tags
23 ## outgoing changesets between tags
24 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
24 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
25 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
25 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
26 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
26 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
28 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
28 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
29 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
29 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
30 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
30 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
31
31
32 response.mustcontain('11 files changed with 94 insertions and 64 deletions')
32 response.mustcontain('11 files changed with 94 insertions and 64 deletions')
33
33
34 ## files diff
34 ## files diff
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
44 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
44 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
45 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
45 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
46
46
47 def test_compare_tag_git(self):
47 def test_compare_tag_git(self):
48 self.log_user()
48 self.log_user()
49 tag1 = 'v0.1.2'
49 tag1 = 'v0.1.2'
50 tag2 = 'v0.1.3'
50 tag2 = 'v0.1.3'
51 response = self.app.get(url(controller='compare', action='index',
51 response = self.app.get(url(controller='compare', action='index',
52 repo_name=GIT_REPO,
52 repo_name=GIT_REPO,
53 org_ref_type="tag",
53 org_ref_type="tag",
54 org_ref=tag1,
54 org_ref=tag1,
55 other_ref_type="tag",
55 other_ref_type="tag",
56 other_ref=tag2,
56 other_ref=tag2,
57 bundle=False
58 ))
57 ))
59 response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
58 response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
60
59
61 ## outgoing changesets between tags
60 ## outgoing changesets between tags
62 response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
61 response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
63 response.mustcontain('''<a href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % GIT_REPO)
62 response.mustcontain('''<a href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % GIT_REPO)
64 response.mustcontain('''<a href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % GIT_REPO)
63 response.mustcontain('''<a href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % GIT_REPO)
65 response.mustcontain('''<a href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % GIT_REPO)
64 response.mustcontain('''<a href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % GIT_REPO)
66 response.mustcontain('''<a href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % GIT_REPO)
65 response.mustcontain('''<a href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % GIT_REPO)
67 response.mustcontain('''<a href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % GIT_REPO)
66 response.mustcontain('''<a href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % GIT_REPO)
68 response.mustcontain('''<a href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % GIT_REPO)
67 response.mustcontain('''<a href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % GIT_REPO)
69
68
70 response.mustcontain('11 files changed with 94 insertions and 64 deletions')
69 response.mustcontain('11 files changed with 94 insertions and 64 deletions')
71
70
72 #files
71 #files
73 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a>''' % (GIT_REPO, tag1, tag2))
72 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a>''' % (GIT_REPO, tag1, tag2))
74 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a>''' % (GIT_REPO, tag1, tag2))
73 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a>''' % (GIT_REPO, tag1, tag2))
75 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a>''' % (GIT_REPO, tag1, tag2))
74 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a>''' % (GIT_REPO, tag1, tag2))
76 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a>''' % (GIT_REPO, tag1, tag2))
75 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a>''' % (GIT_REPO, tag1, tag2))
77 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a>''' % (GIT_REPO, tag1, tag2))
76 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a>''' % (GIT_REPO, tag1, tag2))
78 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a>''' % (GIT_REPO, tag1, tag2))
77 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a>''' % (GIT_REPO, tag1, tag2))
79 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>''' % (GIT_REPO, tag1, tag2))
78 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>''' % (GIT_REPO, tag1, tag2))
80 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a>''' % (GIT_REPO, tag1, tag2))
79 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a>''' % (GIT_REPO, tag1, tag2))
81 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a>''' % (GIT_REPO, tag1, tag2))
80 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a>''' % (GIT_REPO, tag1, tag2))
82 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a>''' % (GIT_REPO, tag1, tag2))
81 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a>''' % (GIT_REPO, tag1, tag2))
83 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a>''' % (GIT_REPO, tag1, tag2))
82 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a>''' % (GIT_REPO, tag1, tag2))
84
83
85 def test_index_branch_hg(self):
84 def test_index_branch_hg(self):
86 self.log_user()
85 self.log_user()
87 response = self.app.get(url(controller='compare', action='index',
86 response = self.app.get(url(controller='compare', action='index',
88 repo_name=HG_REPO,
87 repo_name=HG_REPO,
89 org_ref_type="branch",
88 org_ref_type="branch",
90 org_ref='default',
89 org_ref='default',
91 other_ref_type="branch",
90 other_ref_type="branch",
92 other_ref='default',
91 other_ref='default',
93 ))
92 ))
94
93
95 response.mustcontain('%s@default -&gt; %s@default' % (HG_REPO, HG_REPO))
94 response.mustcontain('%s@default -&gt; %s@default' % (HG_REPO, HG_REPO))
96 # branch are equal
95 # branch are equal
97 response.mustcontain('<span class="empty_data">No files</span>')
96 response.mustcontain('<span class="empty_data">No files</span>')
98 response.mustcontain('<span class="empty_data">No changesets</span>')
97 response.mustcontain('<span class="empty_data">No changesets</span>')
99
98
100 def test_index_branch_git(self):
99 def test_index_branch_git(self):
101 self.log_user()
100 self.log_user()
102 response = self.app.get(url(controller='compare', action='index',
101 response = self.app.get(url(controller='compare', action='index',
103 repo_name=GIT_REPO,
102 repo_name=GIT_REPO,
104 org_ref_type="branch",
103 org_ref_type="branch",
105 org_ref='master',
104 org_ref='master',
106 other_ref_type="branch",
105 other_ref_type="branch",
107 other_ref='master',
106 other_ref='master',
108 ))
107 ))
109
108
110 response.mustcontain('%s@master -&gt; %s@master' % (GIT_REPO, GIT_REPO))
109 response.mustcontain('%s@master -&gt; %s@master' % (GIT_REPO, GIT_REPO))
111 # branch are equal
110 # branch are equal
112 response.mustcontain('<span class="empty_data">No files</span>')
111 response.mustcontain('<span class="empty_data">No files</span>')
113 response.mustcontain('<span class="empty_data">No changesets</span>')
112 response.mustcontain('<span class="empty_data">No changesets</span>')
114
113
115 def test_compare_revisions_hg(self):
114 def test_compare_revisions_hg(self):
116 self.log_user()
115 self.log_user()
117 rev1 = 'b986218ba1c9'
116 rev1 = 'b986218ba1c9'
118 rev2 = '3d8f361e72ab'
117 rev2 = '3d8f361e72ab'
119
118
120 response = self.app.get(url(controller='compare', action='index',
119 response = self.app.get(url(controller='compare', action='index',
121 repo_name=HG_REPO,
120 repo_name=HG_REPO,
122 org_ref_type="rev",
121 org_ref_type="rev",
123 org_ref=rev1,
122 org_ref=rev1,
124 other_ref_type="rev",
123 other_ref_type="rev",
125 other_ref=rev2,
124 other_ref=rev2,
126 ))
125 ))
127 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
126 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
128 ## outgoing changesets between those revisions
127 ## outgoing changesets between those revisions
129 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
128 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
130
129
131 response.mustcontain('1 file changed with 7 insertions and 0 deletions')
130 response.mustcontain('1 file changed with 7 insertions and 0 deletions')
132 ## files
131 ## files
133 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
132 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
134
133
135 def test_compare_revisions_git(self):
134 def test_compare_revisions_git(self):
136 self.log_user()
135 self.log_user()
137 rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
136 rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
138 rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
137 rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
139
138
140 response = self.app.get(url(controller='compare', action='index',
139 response = self.app.get(url(controller='compare', action='index',
141 repo_name=GIT_REPO,
140 repo_name=GIT_REPO,
142 org_ref_type="rev",
141 org_ref_type="rev",
143 org_ref=rev1,
142 org_ref=rev1,
144 other_ref_type="rev",
143 other_ref_type="rev",
145 other_ref=rev2,
144 other_ref=rev2,
146 ))
145 ))
147 response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
146 response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
148 ## outgoing changesets between those revisions
147 ## outgoing changesets between those revisions
149 response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
148 response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
150 response.mustcontain('1 file changed with 7 insertions and 0 deletions')
149 response.mustcontain('1 file changed with 7 insertions and 0 deletions')
151
150
152 ## files
151 ## files
153 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (GIT_REPO, rev1, rev2))
152 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (GIT_REPO, rev1, rev2))
General Comments 0
You need to be logged in to leave comments. Login now