##// END OF EJS Templates
Improved cross repos diffs...
marcink -
r2362:3c4afb88 codereview
parent child Browse files
Show More
@@ -1,159 +1,170 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 def _handle_ref(self, ref):
52 def _handle_ref(self, ref):
53 """
53 """
54 Parse the org...other string
54 Parse the org...other string
55 Possible formats are
55 Possible formats are
56 `(branch|book|tag):<name>...(branch|book|tag):<othername>`
56 `(branch|book|tag):<name>...(branch|book|tag):<othername>`
57
57
58 :param ref: <orginal_reference>...<other_reference>
58 :param ref: <orginal_reference>...<other_reference>
59 :type ref: str
59 :type ref: str
60 """
60 """
61 org_repo = c.rhodecode_repo.name
61 org_repo = c.rhodecode_db_repo.repo_name
62
62
63 def org_parser(org):
63 def org_parser(org):
64 _repo = org_repo
64 _repo = org_repo
65 name, val = org.split(':')
65 name, val = org.split(':')
66 return _repo, (name, val)
66 return _repo, (name, val)
67
67
68 def other_parser(other):
68 def other_parser(other):
69 _other_repo = request.GET.get('repo')
69 _other_repo = request.GET.get('repo')
70 _repo = org_repo
70 _repo = org_repo
71 name, val = other.split(':')
71 name, val = other.split(':')
72 if _other_repo:
72 if _other_repo:
73 #TODO: do an actual repo loookup within rhodecode
74 _repo = _other_repo
73 _repo = _other_repo
75
74
76 return _repo, (name, val)
75 return _repo, (name, val)
77
76
78 if '...' in ref:
77 if '...' in ref:
79 try:
78 try:
80 org, other = ref.split('...')
79 org, other = ref.split('...')
81 org_repo, org_ref = org_parser(org)
80 org_repo, org_ref = org_parser(org)
82 other_repo, other_ref = other_parser(other)
81 other_repo, other_ref = other_parser(other)
83 return org_repo, org_ref, other_repo, other_ref
82 return org_repo, org_ref, other_repo, other_ref
84 except:
83 except:
85 log.error(traceback.format_exc())
84 log.error(traceback.format_exc())
86
85
87 raise HTTPNotFound
86 raise HTTPNotFound
88
87
89 def _get_discovery(self,org_repo, org_ref, other_repo, other_ref):
88 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
90 from mercurial import discovery
89 from mercurial import discovery
91 other = org_repo._repo
90 other = org_repo._repo
92 repo = other_repo._repo
91 repo = other_repo._repo
92 tip = other[org_ref[1]]
93 log.debug('Doing discovery for %s@%s vs %s@%s' % (
94 org_repo, org_ref, other_repo, other_ref)
95 )
96 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
93 tmp = discovery.findcommonincoming(
97 tmp = discovery.findcommonincoming(
94 repo=repo, # other_repo we check for incoming
98 repo=repo, # other_repo we check for incoming
95 remote=other, # org_repo source for incoming
99 remote=other, # org_repo source for incoming
96 heads=[other[org_ref[1]].node()],
100 heads=[tip.node()],
97 force=False
101 force=False
98 )
102 )
99 return tmp
103 return tmp
100
104
101 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
105 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
102 changesets = []
106 changesets = []
103 #case two independent repos
107 #case two independent repos
104 if org_repo != other_repo:
108 if org_repo != other_repo:
105 common, incoming, rheads = tmp
109 common, incoming, rheads = tmp
106
110
107 if not incoming:
111 if not incoming:
108 revs = []
112 revs = []
109 else:
113 else:
110 revs = org_repo._repo.changelog.findmissing(common, rheads)
114 revs = org_repo._repo.changelog.findmissing(common, rheads)
111
115
112 for cs in reversed(map(binascii.hexlify, revs)):
116 for cs in reversed(map(binascii.hexlify, revs)):
113 changesets.append(org_repo.get_changeset(cs))
117 changesets.append(org_repo.get_changeset(cs))
114 else:
118 else:
115 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
119 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
116 other_ref[1])]
120 other_ref[1])]
117 from mercurial import scmutil
121 from mercurial import scmutil
118 out = scmutil.revrange(org_repo._repo, revs)
122 out = scmutil.revrange(org_repo._repo, revs)
119 for cs in reversed(out):
123 for cs in reversed(out):
120 changesets.append(org_repo.get_changeset(cs))
124 changesets.append(org_repo.get_changeset(cs))
121
125
122 return changesets
126 return changesets
123
127
124 def index(self, ref):
128 def index(self, ref):
125 org_repo, org_ref, other_repo, other_ref = self._handle_ref(ref)
129 org_repo, org_ref, other_repo, other_ref = self._handle_ref(ref)
130
126 c.swap_url = h.url('compare_home', repo_name=other_repo,
131 c.swap_url = h.url('compare_home', repo_name=other_repo,
127 ref='%s...%s' % (':'.join(other_ref),
132 ref='%s...%s' % (':'.join(other_ref),
128 ':'.join(org_ref)),
133 ':'.join(org_ref)),
129 repo=org_repo)
134 repo=org_repo)
130 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
135 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
131 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
136 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
132 tmp = self._get_discovery(org_repo.scm_instance,
137
138 if c.org_repo is None or c.other_repo is None:
139 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
140 raise HTTPNotFound
141
142 discovery_data = self._get_discovery(org_repo.scm_instance,
133 org_ref,
143 org_ref,
134 other_repo.scm_instance,
144 other_repo.scm_instance,
135 other_ref)
145 other_ref)
136 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
146 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
137 org_ref,
147 org_ref,
138 other_repo.scm_instance,
148 other_repo.scm_instance,
139 other_ref,
149 other_ref,
140 tmp)
150 discovery_data)
141
151
142 c.org_ref = org_ref[1]
152 c.org_ref = org_ref[1]
143 c.other_ref = other_ref[1]
153 c.other_ref = other_ref[1]
144 # diff needs to have swapped org with other to generate proper diff
154 # diff needs to have swapped org with other to generate proper diff
145 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref, tmp)
155 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
156 discovery_data)
146 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
157 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
147 _parsed = diff_processor.prepare()
158 _parsed = diff_processor.prepare()
148
159
149 c.files = []
160 c.files = []
150 c.changes = {}
161 c.changes = {}
151 # sort Added first then Modified last Deleted files
162 # sort Added first then Modified last Deleted files
152 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
163 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
153 for f in sorted(_parsed, key=sorter):
164 for f in sorted(_parsed, key=sorter):
154 fid = h.FID('', f['filename'])
165 fid = h.FID('', f['filename'])
155 c.files.append([fid, f['operation'], f['filename'], f['stats']])
166 c.files.append([fid, f['operation'], f['filename'], f['stats']])
156 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
167 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
157 c.changes[fid] = [f['operation'], f['filename'], diff]
168 c.changes[fid] = [f['operation'], f['filename'], diff]
158
169
159 return render('compare/compare_diff.html')
170 return render('compare/compare_diff.html')
@@ -1,614 +1,615 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 io
29 import io
30 import difflib
30 import difflib
31 import markupsafe
31 import markupsafe
32
32
33 from itertools import tee, imap
33 from itertools import tee, imap
34
34
35 from mercurial import patch
35 from mercurial import patch
36 from mercurial.mdiff import diffopts
36 from mercurial.mdiff import diffopts
37 from mercurial.bundlerepo import bundlerepository
37 from mercurial.bundlerepo import bundlerepository
38 from mercurial import localrepo
38 from mercurial import localrepo
39
39
40 from pylons.i18n.translation import _
40 from pylons.i18n.translation import _
41
41
42 from rhodecode.lib.vcs.exceptions import VCSError
42 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 from rhodecode.lib.helpers import escape
44 from rhodecode.lib.helpers import escape
45 from rhodecode.lib.utils import EmptyChangeset, make_ui
45 from rhodecode.lib.utils import EmptyChangeset, make_ui
46
46
47
47
48 def wrap_to_table(str_):
48 def wrap_to_table(str_):
49 return '''<table class="code-difftable">
49 return '''<table class="code-difftable">
50 <tr class="line no-comment">
50 <tr class="line no-comment">
51 <td class="lineno new"></td>
51 <td class="lineno new"></td>
52 <td class="code no-comment"><pre>%s</pre></td>
52 <td class="code no-comment"><pre>%s</pre></td>
53 </tr>
53 </tr>
54 </table>''' % str_
54 </table>''' % str_
55
55
56
56
57 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
57 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
58 ignore_whitespace=True, line_context=3,
58 ignore_whitespace=True, line_context=3,
59 enable_comments=False):
59 enable_comments=False):
60 """
60 """
61 returns a wrapped diff into a table, checks for cut_off_limit and presents
61 returns a wrapped diff into a table, checks for cut_off_limit and presents
62 proper message
62 proper message
63 """
63 """
64
64
65 if filenode_old is None:
65 if filenode_old is None:
66 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
66 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
67
67
68 if filenode_old.is_binary or filenode_new.is_binary:
68 if filenode_old.is_binary or filenode_new.is_binary:
69 diff = wrap_to_table(_('binary file'))
69 diff = wrap_to_table(_('binary file'))
70 stats = (0, 0)
70 stats = (0, 0)
71 size = 0
71 size = 0
72
72
73 elif cut_off_limit != -1 and (cut_off_limit is None or
73 elif cut_off_limit != -1 and (cut_off_limit is None or
74 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
74 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
75
75
76 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
76 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
77 ignore_whitespace=ignore_whitespace,
77 ignore_whitespace=ignore_whitespace,
78 context=line_context)
78 context=line_context)
79 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
79 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
80
80
81 diff = diff_processor.as_html(enable_comments=enable_comments)
81 diff = diff_processor.as_html(enable_comments=enable_comments)
82 stats = diff_processor.stat()
82 stats = diff_processor.stat()
83 size = len(diff or '')
83 size = len(diff or '')
84 else:
84 else:
85 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
85 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
86 'diff menu to display this diff'))
86 'diff menu to display this diff'))
87 stats = (0, 0)
87 stats = (0, 0)
88 size = 0
88 size = 0
89 if not diff:
89 if not diff:
90 submodules = filter(lambda o: isinstance(o, SubModuleNode),
90 submodules = filter(lambda o: isinstance(o, SubModuleNode),
91 [filenode_new, filenode_old])
91 [filenode_new, filenode_old])
92 if submodules:
92 if submodules:
93 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
93 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
94 else:
94 else:
95 diff = wrap_to_table(_('No changes detected'))
95 diff = wrap_to_table(_('No changes detected'))
96
96
97 cs1 = filenode_old.changeset.raw_id
97 cs1 = filenode_old.changeset.raw_id
98 cs2 = filenode_new.changeset.raw_id
98 cs2 = filenode_new.changeset.raw_id
99
99
100 return size, cs1, cs2, diff, stats
100 return size, cs1, cs2, diff, stats
101
101
102
102
103 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
103 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
104 """
104 """
105 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
105 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
106
106
107 :param ignore_whitespace: ignore whitespaces in diff
107 :param ignore_whitespace: ignore whitespaces in diff
108 """
108 """
109 # make sure we pass in default context
109 # make sure we pass in default context
110 context = context or 3
110 context = context or 3
111 submodules = filter(lambda o: isinstance(o, SubModuleNode),
111 submodules = filter(lambda o: isinstance(o, SubModuleNode),
112 [filenode_new, filenode_old])
112 [filenode_new, filenode_old])
113 if submodules:
113 if submodules:
114 return ''
114 return ''
115
115
116 for filenode in (filenode_old, filenode_new):
116 for filenode in (filenode_old, filenode_new):
117 if not isinstance(filenode, FileNode):
117 if not isinstance(filenode, FileNode):
118 raise VCSError("Given object should be FileNode object, not %s"
118 raise VCSError("Given object should be FileNode object, not %s"
119 % filenode.__class__)
119 % filenode.__class__)
120
120
121 repo = filenode_new.changeset.repository
121 repo = filenode_new.changeset.repository
122 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
122 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
123 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
123 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
124
124
125 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
125 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
126 ignore_whitespace, context)
126 ignore_whitespace, context)
127 return vcs_gitdiff
127 return vcs_gitdiff
128
128
129
129
130 class DiffProcessor(object):
130 class DiffProcessor(object):
131 """
131 """
132 Give it a unified diff and it returns a list of the files that were
132 Give it a unified diff and it returns a list of the files that were
133 mentioned in the diff together with a dict of meta information that
133 mentioned in the diff together with a dict of meta information that
134 can be used to render it in a HTML template.
134 can be used to render it in a HTML template.
135 """
135 """
136 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
136 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
137
137
138 def __init__(self, diff, differ='diff', format='udiff'):
138 def __init__(self, diff, differ='diff', format='udiff'):
139 """
139 """
140 :param diff: a text in diff format or generator
140 :param diff: a text in diff format or generator
141 :param format: format of diff passed, `udiff` or `gitdiff`
141 :param format: format of diff passed, `udiff` or `gitdiff`
142 """
142 """
143 if isinstance(diff, basestring):
143 if isinstance(diff, basestring):
144 diff = [diff]
144 diff = [diff]
145
145
146 self.__udiff = diff
146 self.__udiff = diff
147 self.__format = format
147 self.__format = format
148 self.adds = 0
148 self.adds = 0
149 self.removes = 0
149 self.removes = 0
150
150
151 if isinstance(self.__udiff, basestring):
151 if isinstance(self.__udiff, basestring):
152 self.lines = iter(self.__udiff.splitlines(1))
152 self.lines = iter(self.__udiff.splitlines(1))
153
153
154 elif self.__format == 'gitdiff':
154 elif self.__format == 'gitdiff':
155 udiff_copy = self.copy_iterator()
155 udiff_copy = self.copy_iterator()
156 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
156 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
157 else:
157 else:
158 udiff_copy = self.copy_iterator()
158 udiff_copy = self.copy_iterator()
159 self.lines = imap(self.escaper, udiff_copy)
159 self.lines = imap(self.escaper, udiff_copy)
160
160
161 # Select a differ.
161 # Select a differ.
162 if differ == 'difflib':
162 if differ == 'difflib':
163 self.differ = self._highlight_line_difflib
163 self.differ = self._highlight_line_difflib
164 else:
164 else:
165 self.differ = self._highlight_line_udiff
165 self.differ = self._highlight_line_udiff
166
166
167 def escaper(self, string):
167 def escaper(self, string):
168 return markupsafe.escape(string)
168 return markupsafe.escape(string)
169
169
170 def copy_iterator(self):
170 def copy_iterator(self):
171 """
171 """
172 make a fresh copy of generator, we should not iterate thru
172 make a fresh copy of generator, we should not iterate thru
173 an original as it's needed for repeating operations on
173 an original as it's needed for repeating operations on
174 this instance of DiffProcessor
174 this instance of DiffProcessor
175 """
175 """
176 self.__udiff, iterator_copy = tee(self.__udiff)
176 self.__udiff, iterator_copy = tee(self.__udiff)
177 return iterator_copy
177 return iterator_copy
178
178
179 def _extract_rev(self, line1, line2):
179 def _extract_rev(self, line1, line2):
180 """
180 """
181 Extract the operation (A/M/D), filename and revision hint from a line.
181 Extract the operation (A/M/D), filename and revision hint from a line.
182 """
182 """
183
183
184 try:
184 try:
185 if line1.startswith('--- ') and line2.startswith('+++ '):
185 if line1.startswith('--- ') and line2.startswith('+++ '):
186 l1 = line1[4:].split(None, 1)
186 l1 = line1[4:].split(None, 1)
187 old_filename = (l1[0].replace('a/', '', 1)
187 old_filename = (l1[0].replace('a/', '', 1)
188 if len(l1) >= 1 else None)
188 if len(l1) >= 1 else None)
189 old_rev = l1[1] if len(l1) == 2 else 'old'
189 old_rev = l1[1] if len(l1) == 2 else 'old'
190
190
191 l2 = line2[4:].split(None, 1)
191 l2 = line2[4:].split(None, 1)
192 new_filename = (l2[0].replace('b/', '', 1)
192 new_filename = (l2[0].replace('b/', '', 1)
193 if len(l1) >= 1 else None)
193 if len(l1) >= 1 else None)
194 new_rev = l2[1] if len(l2) == 2 else 'new'
194 new_rev = l2[1] if len(l2) == 2 else 'new'
195
195
196 filename = (old_filename
196 filename = (old_filename
197 if old_filename != '/dev/null' else new_filename)
197 if old_filename != '/dev/null' else new_filename)
198
198
199 operation = 'D' if new_filename == '/dev/null' else None
199 operation = 'D' if new_filename == '/dev/null' else None
200 if not operation:
200 if not operation:
201 operation = 'M' if old_filename != '/dev/null' else 'A'
201 operation = 'M' if old_filename != '/dev/null' else 'A'
202
202
203 return operation, filename, new_rev, old_rev
203 return operation, filename, new_rev, old_rev
204 except (ValueError, IndexError):
204 except (ValueError, IndexError):
205 pass
205 pass
206
206
207 return None, None, None, None
207 return None, None, None, None
208
208
209 def _parse_gitdiff(self, diffiterator):
209 def _parse_gitdiff(self, diffiterator):
210 def line_decoder(l):
210 def line_decoder(l):
211 if l.startswith('+') and not l.startswith('+++'):
211 if l.startswith('+') and not l.startswith('+++'):
212 self.adds += 1
212 self.adds += 1
213 elif l.startswith('-') and not l.startswith('---'):
213 elif l.startswith('-') and not l.startswith('---'):
214 self.removes += 1
214 self.removes += 1
215 return l.decode('utf8', 'replace')
215 return l.decode('utf8', 'replace')
216
216
217 output = list(diffiterator)
217 output = list(diffiterator)
218 size = len(output)
218 size = len(output)
219
219
220 if size == 2:
220 if size == 2:
221 l = []
221 l = []
222 l.extend([output[0]])
222 l.extend([output[0]])
223 l.extend(output[1].splitlines(1))
223 l.extend(output[1].splitlines(1))
224 return map(line_decoder, l)
224 return map(line_decoder, l)
225 elif size == 1:
225 elif size == 1:
226 return map(line_decoder, output[0].splitlines(1))
226 return map(line_decoder, output[0].splitlines(1))
227 elif size == 0:
227 elif size == 0:
228 return []
228 return []
229
229
230 raise Exception('wrong size of diff %s' % size)
230 raise Exception('wrong size of diff %s' % size)
231
231
232 def _highlight_line_difflib(self, line, next_):
232 def _highlight_line_difflib(self, line, next_):
233 """
233 """
234 Highlight inline changes in both lines.
234 Highlight inline changes in both lines.
235 """
235 """
236
236
237 if line['action'] == 'del':
237 if line['action'] == 'del':
238 old, new = line, next_
238 old, new = line, next_
239 else:
239 else:
240 old, new = next_, line
240 old, new = next_, line
241
241
242 oldwords = re.split(r'(\W)', old['line'])
242 oldwords = re.split(r'(\W)', old['line'])
243 newwords = re.split(r'(\W)', new['line'])
243 newwords = re.split(r'(\W)', new['line'])
244
244
245 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
245 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
246
246
247 oldfragments, newfragments = [], []
247 oldfragments, newfragments = [], []
248 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
248 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
249 oldfrag = ''.join(oldwords[i1:i2])
249 oldfrag = ''.join(oldwords[i1:i2])
250 newfrag = ''.join(newwords[j1:j2])
250 newfrag = ''.join(newwords[j1:j2])
251 if tag != 'equal':
251 if tag != 'equal':
252 if oldfrag:
252 if oldfrag:
253 oldfrag = '<del>%s</del>' % oldfrag
253 oldfrag = '<del>%s</del>' % oldfrag
254 if newfrag:
254 if newfrag:
255 newfrag = '<ins>%s</ins>' % newfrag
255 newfrag = '<ins>%s</ins>' % newfrag
256 oldfragments.append(oldfrag)
256 oldfragments.append(oldfrag)
257 newfragments.append(newfrag)
257 newfragments.append(newfrag)
258
258
259 old['line'] = "".join(oldfragments)
259 old['line'] = "".join(oldfragments)
260 new['line'] = "".join(newfragments)
260 new['line'] = "".join(newfragments)
261
261
262 def _highlight_line_udiff(self, line, next_):
262 def _highlight_line_udiff(self, line, next_):
263 """
263 """
264 Highlight inline changes in both lines.
264 Highlight inline changes in both lines.
265 """
265 """
266 start = 0
266 start = 0
267 limit = min(len(line['line']), len(next_['line']))
267 limit = min(len(line['line']), len(next_['line']))
268 while start < limit and line['line'][start] == next_['line'][start]:
268 while start < limit and line['line'][start] == next_['line'][start]:
269 start += 1
269 start += 1
270 end = -1
270 end = -1
271 limit -= start
271 limit -= start
272 while -end <= limit and line['line'][end] == next_['line'][end]:
272 while -end <= limit and line['line'][end] == next_['line'][end]:
273 end -= 1
273 end -= 1
274 end += 1
274 end += 1
275 if start or end:
275 if start or end:
276 def do(l):
276 def do(l):
277 last = end + len(l['line'])
277 last = end + len(l['line'])
278 if l['action'] == 'add':
278 if l['action'] == 'add':
279 tag = 'ins'
279 tag = 'ins'
280 else:
280 else:
281 tag = 'del'
281 tag = 'del'
282 l['line'] = '%s<%s>%s</%s>%s' % (
282 l['line'] = '%s<%s>%s</%s>%s' % (
283 l['line'][:start],
283 l['line'][:start],
284 tag,
284 tag,
285 l['line'][start:last],
285 l['line'][start:last],
286 tag,
286 tag,
287 l['line'][last:]
287 l['line'][last:]
288 )
288 )
289 do(line)
289 do(line)
290 do(next_)
290 do(next_)
291
291
292 def _parse_udiff(self):
292 def _parse_udiff(self):
293 """
293 """
294 Parse the diff an return data for the template.
294 Parse the diff an return data for the template.
295 """
295 """
296 lineiter = self.lines
296 lineiter = self.lines
297 files = []
297 files = []
298 try:
298 try:
299 line = lineiter.next()
299 line = lineiter.next()
300 while 1:
300 while 1:
301 # continue until we found the old file
301 # continue until we found the old file
302 if not line.startswith('--- '):
302 if not line.startswith('--- '):
303 line = lineiter.next()
303 line = lineiter.next()
304 continue
304 continue
305
305
306 chunks = []
306 chunks = []
307 stats = [0, 0]
307 stats = [0, 0]
308 operation, filename, old_rev, new_rev = \
308 operation, filename, old_rev, new_rev = \
309 self._extract_rev(line, lineiter.next())
309 self._extract_rev(line, lineiter.next())
310 files.append({
310 files.append({
311 'filename': filename,
311 'filename': filename,
312 'old_revision': old_rev,
312 'old_revision': old_rev,
313 'new_revision': new_rev,
313 'new_revision': new_rev,
314 'chunks': chunks,
314 'chunks': chunks,
315 'operation': operation,
315 'operation': operation,
316 'stats': stats,
316 'stats': stats,
317 })
317 })
318
318
319 line = lineiter.next()
319 line = lineiter.next()
320 while line:
320 while line:
321 match = self._chunk_re.match(line)
321 match = self._chunk_re.match(line)
322 if not match:
322 if not match:
323 break
323 break
324
324
325 lines = []
325 lines = []
326 chunks.append(lines)
326 chunks.append(lines)
327
327
328 old_line, old_end, new_line, new_end = \
328 old_line, old_end, new_line, new_end = \
329 [int(x or 1) for x in match.groups()[:-1]]
329 [int(x or 1) for x in match.groups()[:-1]]
330 old_line -= 1
330 old_line -= 1
331 new_line -= 1
331 new_line -= 1
332 gr = match.groups()
332 gr = match.groups()
333 context = len(gr) == 5
333 context = len(gr) == 5
334 old_end += old_line
334 old_end += old_line
335 new_end += new_line
335 new_end += new_line
336
336
337 if context:
337 if context:
338 # skip context only if it's first line
338 # skip context only if it's first line
339 if int(gr[0]) > 1:
339 if int(gr[0]) > 1:
340 lines.append({
340 lines.append({
341 'old_lineno': '...',
341 'old_lineno': '...',
342 'new_lineno': '...',
342 'new_lineno': '...',
343 'action': 'context',
343 'action': 'context',
344 'line': line,
344 'line': line,
345 })
345 })
346
346
347 line = lineiter.next()
347 line = lineiter.next()
348 while old_line < old_end or new_line < new_end:
348 while old_line < old_end or new_line < new_end:
349 if line:
349 if line:
350 command, line = line[0], line[1:]
350 command, line = line[0], line[1:]
351 else:
351 else:
352 command = ' '
352 command = ' '
353 affects_old = affects_new = False
353 affects_old = affects_new = False
354
354
355 # ignore those if we don't expect them
355 # ignore those if we don't expect them
356 if command in '#@':
356 if command in '#@':
357 continue
357 continue
358 elif command == '+':
358 elif command == '+':
359 affects_new = True
359 affects_new = True
360 action = 'add'
360 action = 'add'
361 stats[0] += 1
361 stats[0] += 1
362 elif command == '-':
362 elif command == '-':
363 affects_old = True
363 affects_old = True
364 action = 'del'
364 action = 'del'
365 stats[1] += 1
365 stats[1] += 1
366 else:
366 else:
367 affects_old = affects_new = True
367 affects_old = affects_new = True
368 action = 'unmod'
368 action = 'unmod'
369
369
370 if line.find('No newline at end of file') != -1:
370 if line.find('No newline at end of file') != -1:
371 lines.append({
371 lines.append({
372 'old_lineno': '...',
372 'old_lineno': '...',
373 'new_lineno': '...',
373 'new_lineno': '...',
374 'action': 'context',
374 'action': 'context',
375 'line': line
375 'line': line
376 })
376 })
377
377
378 else:
378 else:
379 old_line += affects_old
379 old_line += affects_old
380 new_line += affects_new
380 new_line += affects_new
381 lines.append({
381 lines.append({
382 'old_lineno': affects_old and old_line or '',
382 'old_lineno': affects_old and old_line or '',
383 'new_lineno': affects_new and new_line or '',
383 'new_lineno': affects_new and new_line or '',
384 'action': action,
384 'action': action,
385 'line': line
385 'line': line
386 })
386 })
387
387
388 line = lineiter.next()
388 line = lineiter.next()
389 except StopIteration:
389 except StopIteration:
390 pass
390 pass
391
391
392 # highlight inline changes
392 # highlight inline changes
393 for diff_data in files:
393 for diff_data in files:
394 for chunk in diff_data['chunks']:
394 for chunk in diff_data['chunks']:
395 lineiter = iter(chunk)
395 lineiter = iter(chunk)
396 try:
396 try:
397 while 1:
397 while 1:
398 line = lineiter.next()
398 line = lineiter.next()
399 if line['action'] != 'unmod':
399 if line['action'] != 'unmod':
400 nextline = lineiter.next()
400 nextline = lineiter.next()
401 if nextline['action'] in ['unmod', 'context'] or \
401 if nextline['action'] in ['unmod', 'context'] or \
402 nextline['action'] == line['action']:
402 nextline['action'] == line['action']:
403 continue
403 continue
404 self.differ(line, nextline)
404 self.differ(line, nextline)
405 except StopIteration:
405 except StopIteration:
406 pass
406 pass
407 return files
407 return files
408
408
409 def prepare(self):
409 def prepare(self):
410 """
410 """
411 Prepare the passed udiff for HTML rendering. It'l return a list
411 Prepare the passed udiff for HTML rendering. It'l return a list
412 of dicts
412 of dicts
413 """
413 """
414 return self._parse_udiff()
414 return self._parse_udiff()
415
415
416 def _safe_id(self, idstring):
416 def _safe_id(self, idstring):
417 """Make a string safe for including in an id attribute.
417 """Make a string safe for including in an id attribute.
418
418
419 The HTML spec says that id attributes 'must begin with
419 The HTML spec says that id attributes 'must begin with
420 a letter ([A-Za-z]) and may be followed by any number
420 a letter ([A-Za-z]) and may be followed by any number
421 of letters, digits ([0-9]), hyphens ("-"), underscores
421 of letters, digits ([0-9]), hyphens ("-"), underscores
422 ("_"), colons (":"), and periods (".")'. These regexps
422 ("_"), colons (":"), and periods (".")'. These regexps
423 are slightly over-zealous, in that they remove colons
423 are slightly over-zealous, in that they remove colons
424 and periods unnecessarily.
424 and periods unnecessarily.
425
425
426 Whitespace is transformed into underscores, and then
426 Whitespace is transformed into underscores, and then
427 anything which is not a hyphen or a character that
427 anything which is not a hyphen or a character that
428 matches \w (alphanumerics and underscore) is removed.
428 matches \w (alphanumerics and underscore) is removed.
429
429
430 """
430 """
431 # Transform all whitespace to underscore
431 # Transform all whitespace to underscore
432 idstring = re.sub(r'\s', "_", '%s' % idstring)
432 idstring = re.sub(r'\s', "_", '%s' % idstring)
433 # Remove everything that is not a hyphen or a member of \w
433 # Remove everything that is not a hyphen or a member of \w
434 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
434 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
435 return idstring
435 return idstring
436
436
437 def raw_diff(self):
437 def raw_diff(self):
438 """
438 """
439 Returns raw string as udiff
439 Returns raw string as udiff
440 """
440 """
441 udiff_copy = self.copy_iterator()
441 udiff_copy = self.copy_iterator()
442 if self.__format == 'gitdiff':
442 if self.__format == 'gitdiff':
443 udiff_copy = self._parse_gitdiff(udiff_copy)
443 udiff_copy = self._parse_gitdiff(udiff_copy)
444 return u''.join(udiff_copy)
444 return u''.join(udiff_copy)
445
445
446 def as_html(self, table_class='code-difftable', line_class='line',
446 def as_html(self, table_class='code-difftable', line_class='line',
447 new_lineno_class='lineno old', old_lineno_class='lineno new',
447 new_lineno_class='lineno old', old_lineno_class='lineno new',
448 code_class='code', enable_comments=False, diff_lines=None):
448 code_class='code', enable_comments=False, diff_lines=None):
449 """
449 """
450 Return given diff as html table with customized css classes
450 Return given diff as html table with customized css classes
451 """
451 """
452 def _link_to_if(condition, label, url):
452 def _link_to_if(condition, label, url):
453 """
453 """
454 Generates a link if condition is meet or just the label if not.
454 Generates a link if condition is meet or just the label if not.
455 """
455 """
456
456
457 if condition:
457 if condition:
458 return '''<a href="%(url)s">%(label)s</a>''' % {
458 return '''<a href="%(url)s">%(label)s</a>''' % {
459 'url': url,
459 'url': url,
460 'label': label
460 'label': label
461 }
461 }
462 else:
462 else:
463 return label
463 return label
464 if diff_lines is None:
464 if diff_lines is None:
465 diff_lines = self.prepare()
465 diff_lines = self.prepare()
466 _html_empty = True
466 _html_empty = True
467 _html = []
467 _html = []
468 _html.append('''<table class="%(table_class)s">\n''' % {
468 _html.append('''<table class="%(table_class)s">\n''' % {
469 'table_class': table_class
469 'table_class': table_class
470 })
470 })
471 for diff in diff_lines:
471 for diff in diff_lines:
472 for line in diff['chunks']:
472 for line in diff['chunks']:
473 _html_empty = False
473 _html_empty = False
474 for change in line:
474 for change in line:
475 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
475 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
476 'lc': line_class,
476 'lc': line_class,
477 'action': change['action']
477 'action': change['action']
478 })
478 })
479 anchor_old_id = ''
479 anchor_old_id = ''
480 anchor_new_id = ''
480 anchor_new_id = ''
481 anchor_old = "%(filename)s_o%(oldline_no)s" % {
481 anchor_old = "%(filename)s_o%(oldline_no)s" % {
482 'filename': self._safe_id(diff['filename']),
482 'filename': self._safe_id(diff['filename']),
483 'oldline_no': change['old_lineno']
483 'oldline_no': change['old_lineno']
484 }
484 }
485 anchor_new = "%(filename)s_n%(oldline_no)s" % {
485 anchor_new = "%(filename)s_n%(oldline_no)s" % {
486 'filename': self._safe_id(diff['filename']),
486 'filename': self._safe_id(diff['filename']),
487 'oldline_no': change['new_lineno']
487 'oldline_no': change['new_lineno']
488 }
488 }
489 cond_old = (change['old_lineno'] != '...' and
489 cond_old = (change['old_lineno'] != '...' and
490 change['old_lineno'])
490 change['old_lineno'])
491 cond_new = (change['new_lineno'] != '...' and
491 cond_new = (change['new_lineno'] != '...' and
492 change['new_lineno'])
492 change['new_lineno'])
493 if cond_old:
493 if cond_old:
494 anchor_old_id = 'id="%s"' % anchor_old
494 anchor_old_id = 'id="%s"' % anchor_old
495 if cond_new:
495 if cond_new:
496 anchor_new_id = 'id="%s"' % anchor_new
496 anchor_new_id = 'id="%s"' % anchor_new
497 ###########################################################
497 ###########################################################
498 # OLD LINE NUMBER
498 # OLD LINE NUMBER
499 ###########################################################
499 ###########################################################
500 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
500 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
501 'a_id': anchor_old_id,
501 'a_id': anchor_old_id,
502 'olc': old_lineno_class
502 'olc': old_lineno_class
503 })
503 })
504
504
505 _html.append('''%(link)s''' % {
505 _html.append('''%(link)s''' % {
506 'link': _link_to_if(True, change['old_lineno'],
506 'link': _link_to_if(True, change['old_lineno'],
507 '#%s' % anchor_old)
507 '#%s' % anchor_old)
508 })
508 })
509 _html.append('''</td>\n''')
509 _html.append('''</td>\n''')
510 ###########################################################
510 ###########################################################
511 # NEW LINE NUMBER
511 # NEW LINE NUMBER
512 ###########################################################
512 ###########################################################
513
513
514 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
514 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
515 'a_id': anchor_new_id,
515 'a_id': anchor_new_id,
516 'nlc': new_lineno_class
516 'nlc': new_lineno_class
517 })
517 })
518
518
519 _html.append('''%(link)s''' % {
519 _html.append('''%(link)s''' % {
520 'link': _link_to_if(True, change['new_lineno'],
520 'link': _link_to_if(True, change['new_lineno'],
521 '#%s' % anchor_new)
521 '#%s' % anchor_new)
522 })
522 })
523 _html.append('''</td>\n''')
523 _html.append('''</td>\n''')
524 ###########################################################
524 ###########################################################
525 # CODE
525 # CODE
526 ###########################################################
526 ###########################################################
527 comments = '' if enable_comments else 'no-comment'
527 comments = '' if enable_comments else 'no-comment'
528 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
528 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
529 'cc': code_class,
529 'cc': code_class,
530 'inc': comments
530 'inc': comments
531 })
531 })
532 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
532 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
533 'code': change['line']
533 'code': change['line']
534 })
534 })
535 _html.append('''\t</td>''')
535 _html.append('''\t</td>''')
536 _html.append('''\n</tr>\n''')
536 _html.append('''\n</tr>\n''')
537 _html.append('''</table>''')
537 _html.append('''</table>''')
538 if _html_empty:
538 if _html_empty:
539 return None
539 return None
540 return ''.join(_html)
540 return ''.join(_html)
541
541
542 def stat(self):
542 def stat(self):
543 """
543 """
544 Returns tuple of added, and removed lines for this instance
544 Returns tuple of added, and removed lines for this instance
545 """
545 """
546 return self.adds, self.removes
546 return self.adds, self.removes
547
547
548
548
549 class InMemoryBundleRepo(bundlerepository):
550 def __init__(self, ui, path, bundlestream):
551 self._tempparent = None
552 localrepo.localrepository.__init__(self, ui, path)
553 self.ui.setconfig('phases', 'publish', False)
554
555 self.bundle = bundlestream
556
557 # dict with the mapping 'filename' -> position in the bundle
558 self.bundlefilespos = {}
559
560
549 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
561 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
550 """
562 """
551 General differ between branches, bookmarks or separate but releated
563 General differ between branches, bookmarks or separate but releated
552 repositories
564 repositories
553
565
554 :param org_repo:
566 :param org_repo:
555 :type org_repo:
567 :type org_repo:
556 :param org_ref:
568 :param org_ref:
557 :type org_ref:
569 :type org_ref:
558 :param other_repo:
570 :param other_repo:
559 :type other_repo:
571 :type other_repo:
560 :param other_ref:
572 :param other_ref:
561 :type other_ref:
573 :type other_ref:
562 """
574 """
563
575
564 ignore_whitespace = False
576 bundlerepo = ignore_whitespace = False
565 context = 3
577 context = 3
566 org_repo = org_repo.scm_instance._repo
578 org_repo = org_repo.scm_instance._repo
567 other_repo = other_repo.scm_instance._repo
579 other_repo = other_repo.scm_instance._repo
568 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
580 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
569 org_ref = org_ref[1]
581 org_ref = org_ref[1]
570 other_ref = other_ref[1]
582 other_ref = other_ref[1]
571
583
572 if org_repo != other_repo:
584 if org_repo != other_repo:
573
585
574 common, incoming, rheads = discovery_data
586 common, incoming, rheads = discovery_data
587
575 # create a bundle (uncompressed if other repo is not local)
588 # create a bundle (uncompressed if other repo is not local)
576 if other_repo.capable('getbundle'):
589 if other_repo.capable('getbundle') and incoming:
577 # disable repo hooks here since it's just bundle !
590 # disable repo hooks here since it's just bundle !
578 # patch and reset hooks section of UI config to not run any
591 # patch and reset hooks section of UI config to not run any
579 # hooks on fetching archives with subrepos
592 # hooks on fetching archives with subrepos
580 for k, _ in other_repo.ui.configitems('hooks'):
593 for k, _ in other_repo.ui.configitems('hooks'):
581 other_repo.ui.setconfig('hooks', k, None)
594 other_repo.ui.setconfig('hooks', k, None)
582
595
583 unbundle = other_repo.getbundle('incoming', common=common,
596 unbundle = other_repo.getbundle('incoming', common=common,
584 heads=rheads)
597 heads=rheads)
585
598
586 buf = io.BytesIO()
599 buf = io.BytesIO()
587 while True:
600 while True:
588 chunk = unbundle._stream.read(1024*4)
601 chunk = unbundle._stream.read(1024*4)
589 if not chunk:
602 if not chunk:
590 break
603 break
591 buf.write(chunk)
604 buf.write(chunk)
592
605
593 buf.seek(0)
606 buf.seek(0)
594 unbundle._stream = buf
607 unbundle._stream = buf
595
608
596 class InMemoryBundleRepo(bundlerepository):
609 ui = make_ui('db')
597 def __init__(self, ui, path, bundlestream):
610 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
598 self._tempparent = None
611 bundlestream=unbundle)
599 localrepo.localrepository.__init__(self, ui, path)
612 return ''.join(patch.diff(bundlerepo or org_repo, node2=other_ref, opts=opts))
600 self.ui.setconfig('phases', 'publish', False)
601
602 self.bundle = bundlestream
603
604 # dict with the mapping 'filename' -> position in the bundle
605 self.bundlefilespos = {}
606
607 ui = make_ui('db')
608 bundlerepo = InMemoryBundleRepo(ui, path=other_repo.root,
609 bundlestream=unbundle)
610 return ''.join(patch.diff(bundlerepo, node1=org_ref, node2=other_ref,
611 opts=opts))
612 else:
613 else:
613 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
614 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
614 opts=opts))
615 opts=opts))
@@ -1,91 +1,93 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} ${_('Branches')} - ${c.rhodecode_name}
5 ${c.repo_name} ${_('Branches')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
9 <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
10 ${h.link_to(u'Home',h.url('/'))}
10 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('branches')}
14 ${_('branches')}
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('branches')}
18 ${self.menu('branches')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <!-- end box / title -->
27 <!-- end box / title -->
28 %if c.repo_branches:
28 <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;vertical-align: right;text-align: right;"><a href="#" class="ui-btn small">${_('Compare branches')}</a></div>
29 <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;vertical-align: right;text-align: right;"><a href="#" class="ui-btn small">${_('Compare branches')}</a></div>
30 %endif
29 <div class="table">
31 <div class="table">
30 <%include file='branches_data.html'/>
32 <%include file='branches_data.html'/>
31 </div>
33 </div>
32 </div>
34 </div>
33 <script type="text/javascript">
35 <script type="text/javascript">
34 YUE.on('compare_branches','click',function(e){
36 YUE.on('compare_branches','click',function(e){
35 YUE.preventDefault(e);
37 YUE.preventDefault(e);
36 var org = YUQ('input[name=compare_org]:checked')[0];
38 var org = YUQ('input[name=compare_org]:checked')[0];
37 var other = YUQ('input[name=compare_other]:checked')[0];
39 var other = YUQ('input[name=compare_other]:checked')[0];
38 var compare_url = "${h.url('compare_home',repo_name=c.repo_name,ref='__ORG__...__OTHER__')}";
40 var compare_url = "${h.url('compare_home',repo_name=c.repo_name,ref='__ORG__...__OTHER__')}";
39 if(org && other){
41 if(org && other){
40 var u = compare_url.replace('__ORG__','branch:'+org.value)
42 var u = compare_url.replace('__ORG__','branch:'+org.value)
41 .replace('__OTHER__','branch:'+other.value);
43 .replace('__OTHER__','branch:'+other.value);
42 window.location=u;
44 window.location=u;
43 }
45 }
44
46
45 })
47 })
46 // main table sorting
48 // main table sorting
47 var myColumnDefs = [
49 var myColumnDefs = [
48 {key:"name",label:"${_('Name')}",sortable:true},
50 {key:"name",label:"${_('Name')}",sortable:true},
49 {key:"date",label:"${_('Date')}",sortable:true,
51 {key:"date",label:"${_('Date')}",sortable:true,
50 sortOptions: { sortFunction: dateSort }},
52 sortOptions: { sortFunction: dateSort }},
51 {key:"author",label:"${_('Author')}",sortable:true},
53 {key:"author",label:"${_('Author')}",sortable:true},
52 {key:"revision",label:"${_('Revision')}",sortable:true,
54 {key:"revision",label:"${_('Revision')}",sortable:true,
53 sortOptions: { sortFunction: revisionSort }},
55 sortOptions: { sortFunction: revisionSort }},
54 {key:"compare",label:"${_('Compare')}",sortable:false,},
56 {key:"compare",label:"${_('Compare')}",sortable:false,},
55 ];
57 ];
56
58
57 var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
59 var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
58
60
59 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
61 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
60
62
61 myDataSource.responseSchema = {
63 myDataSource.responseSchema = {
62 fields: [
64 fields: [
63 {key:"name"},
65 {key:"name"},
64 {key:"date"},
66 {key:"date"},
65 {key:"author"},
67 {key:"author"},
66 {key:"revision"},
68 {key:"revision"},
67 {key:"compare"},
69 {key:"compare"},
68 ]
70 ]
69 };
71 };
70
72
71 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
73 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
72 {
74 {
73 sortedBy:{key:"name",dir:"asc"},
75 sortedBy:{key:"name",dir:"asc"},
74 MSG_SORTASC:"${_('Click to sort ascending')}",
76 MSG_SORTASC:"${_('Click to sort ascending')}",
75 MSG_SORTDESC:"${_('Click to sort descending')}",
77 MSG_SORTDESC:"${_('Click to sort descending')}",
76 MSG_EMPTY:"${_('No records found.')}",
78 MSG_EMPTY:"${_('No records found.')}",
77 MSG_ERROR:"${_('Data error.')}",
79 MSG_ERROR:"${_('Data error.')}",
78 MSG_LOADING:"${_('Loading...')}",
80 MSG_LOADING:"${_('Loading...')}",
79 }
81 }
80 );
82 );
81 myDataTable.subscribe('postRenderEvent',function(oArgs) {
83 myDataTable.subscribe('postRenderEvent',function(oArgs) {
82 tooltip_activate();
84 tooltip_activate();
83 var func = function(node){
85 var func = function(node){
84 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
86 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
85 }
87 }
86 q_filter('q_filter_branches',YUQ('div.table tr td .logtags .branchtag a'),func);
88 q_filter('q_filter_branches',YUQ('div.table tr td .logtags .branchtag a'),func);
87 });
89 });
88
90
89 </script>
91 </script>
90
92
91 </%def>
93 </%def>
@@ -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')} <a href="${c.swap_url}">swap</a></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)}
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>${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>
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>
42 <td><div class="author">${h.person(cs.author)}</div></td>
42 <td><div class="author">${h.person(cs.author)}</div></td>
43 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
43 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
44 <td>
44 <td>
45 %if hasattr(c,'statuses') and c.statuses:
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>
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
47 %endif
48 </td>
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