Merge lp:~nmb/qbzr/diffview-refactor into lp:qbzr

Proposed by Neil Martinsen-Burrell
Status: Merged
Merged at revision: 1393
Proposed branch: lp:~nmb/qbzr/diffview-refactor
Merge into: lp:qbzr
Diff against target: 308 lines (+179/-104)
1 file modified
lib/diffwindow.py (+179/-104)
To merge this branch: bzr merge lp:~nmb/qbzr/diffview-refactor
Reviewer Review Type Date Requested Status
Alexander Belchenko Needs Information
Review via email: mp+60971@code.launchpad.net

Description of the change

This abstracts the diff loading code from out of DiffWindow so that it can be use in other windows which want to use diff previews. I cherrypicked these changes from lp:~hid-iwata/qbzr/qshelve as a separate revision. This is a standalone change which is separate from the added shelve functionality of any toolbar changes.

To post a comment you must log in.
Revision history for this message
IWATA Hidetaka (hid-iwata) wrote :

This changes are separated from qshelve code originally.

You can merge it simply by doing::
  bzr merge lp:~hid-iwata/qbzr/qshelve -r revid:<email address hidden>

And it would make merging qshelve code more easier.

Regards.

Revision history for this message
Alexander Belchenko (bialix) wrote :

If this is just mechanical extract of the Iwata code, then I think that's ok to land it.

Revision history for this message
Alexander Belchenko (bialix) wrote :

Neil, as I can see the same refactoring is available in qshelve branch, and this is really the first revision in that branch so we can merge it preserving the history.

Revision history for this message
Alexander Belchenko (bialix) wrote :

Neil, as I can see your code is different because you've also added the load method:

+ def load(self):
+ if self._lines is None:
+ self._load_lines()

Why it's needed for you?

review: Needs Information
Revision history for this message
IWATA Hidetaka (hid-iwata) wrote :

It's only used from ShelveListWidget. (it was added by r1360 of qshelve branch.)

> Neil, as I can see your code is different because you've also added the load
> method:
>
>
> + def load(self):
> + if self._lines is None:
> + self._load_lines()
>
> Why it's needed for you?

Revision history for this message
Alexander Belchenko (bialix) wrote :

iwata пишет:
> It's only used from ShelveListWidget. (it was added by r1360 of qshelve branch.)

OK, thanks for clarifications Hidetaka.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/diffwindow.py'
2--- lib/diffwindow.py 2011-05-11 11:49:14 +0000
3+++ lib/diffwindow.py 2011-05-13 21:40:03 +0000
4@@ -126,6 +126,168 @@
5 return 'Unknown tree'
6
7
8+class DiffItem(object):
9+
10+ @classmethod
11+ def create(klass, trees, file_id, paths, changed_content, versioned,
12+ parent, name, kind, executable, filter = None):
13+
14+ if parent == (None, None): # filter out TREE_ROOT (?)
15+ return None
16+
17+ # check for manually deleted files (w/o using bzr rm commands)
18+ if kind[1] is None:
19+ if versioned == (False, True):
20+ # added and missed
21+ return None
22+ if versioned == (True, True):
23+ versioned = (True, False)
24+ paths = (paths[0], None)
25+
26+ renamed = (parent[0], name[0]) != (parent[1], name[1])
27+
28+ dates = [None, None]
29+ for ix in range(2):
30+ if versioned[ix]:
31+ try:
32+ dates[ix] = trees[ix].get_file_mtime(file_id, paths[ix])
33+ except OSError, e:
34+ if not renamed or e.errno != errno.ENOENT:
35+ raise
36+ # If we get ENOENT error then probably we trigger
37+ # bug #251532 in bzrlib. Take current time instead
38+ dates[ix] = time.time()
39+ except FileTimestampUnavailable:
40+ # ghosts around us (see Bug #513096)
41+ dates[ix] = 0 # using 1970/1/1 instead
42+
43+ properties_changed = []
44+ if bool(executable[0]) != bool(executable[1]):
45+ descr = {True: "+x", False: "-x", None: None}
46+ properties_changed.append((descr[executable[0]],
47+ descr[executable[1]]))
48+
49+ if versioned == (True, False):
50+ status = N_('removed')
51+ elif versioned == (False, True):
52+ status = N_('added')
53+ elif renamed and changed_content:
54+ status = N_('renamed and modified')
55+ elif renamed:
56+ status = N_('renamed')
57+ else:
58+ status = N_('modified')
59+ # check filter options
60+ if filter and not filter(status):
61+ return None
62+
63+ return klass(trees, file_id, paths, changed_content, versioned, kind,
64+ properties_changed, dates, status)
65+
66+ def __init__(self, trees, file_id, paths, changed_content, versioned, kind,
67+ properties_changed, dates, status):
68+ self.trees = trees
69+ self.file_id = file_id
70+ self.paths = paths
71+ self.changed_content = changed_content
72+ self.versioned = versioned
73+ self.kind = kind
74+ self.properties_changed = properties_changed
75+ self.dates = dates
76+ self.status = status
77+
78+ self._lines = None
79+ self._binary = None
80+ self._group_cache = {}
81+ self._encodings = [None, None]
82+ self._ulines = [None, None]
83+
84+ def load(self):
85+ if self._lines is None:
86+ self._load_lines()
87+
88+ def _load_lines(self):
89+ if ((self.versioned[0] != self.versioned[1] or self.changed_content)
90+ and (self.kind[0] == 'file' or self.kind[1] == 'file')):
91+ lines = []
92+ binary = False
93+ for ix, tree in enumerate(self.trees):
94+ content = ()
95+ if self.versioned[ix] and self.kind[ix] == 'file':
96+ content = get_file_lines_from_tree(tree, self.file_id)
97+ lines.append(content)
98+ binary = binary or is_binary_content(content)
99+ self._lines = lines
100+ self._binary = binary
101+ else:
102+ self._lines = ((),())
103+ self._binary = False
104+
105+ @property
106+ def lines(self):
107+ if self._lines is None:
108+ self._load_lines()
109+ return self._lines
110+
111+ @property
112+ def binary(self):
113+ if self._binary is None:
114+ self._load_lines()
115+ return self._binary
116+
117+ def groups(self, complete, ignore_whitespace):
118+ key = (complete, ignore_whitespace)
119+ groups = self._group_cache.get(key)
120+ if groups is not None:
121+ return groups
122+
123+ lines = self.lines
124+
125+ if not self.binary:
126+ if self.versioned == (True, False):
127+ groups = [[('delete', 0, len(lines[0]), 0, 0)]]
128+ elif self.versioned == (False, True):
129+ groups = [[('insert', 0, 0, 0, len(lines[1]))]]
130+ else:
131+ groups = self.difference_groups(lines, complete, ignore_whitespace)
132+ else:
133+ groups = []
134+
135+ self._group_cache[key] = groups
136+ return groups
137+
138+ def difference_groups(self, lines, complete, ignore_whitespace):
139+ left, right = lines
140+ if ignore_whitespace:
141+ table = string.maketrans("", "")
142+ strip = lambda l : l.translate(table, string.whitespace)
143+ left = (line.translate(table, string.whitespace) for line in left)
144+ right = (line.translate(table, string.whitespace) for line in right)
145+ matcher = SequenceMatcher(None, left, right)
146+ if complete:
147+ groups = list([matcher.get_opcodes()])
148+ else:
149+ groups = list(matcher.get_grouped_opcodes())
150+
151+ return groups
152+
153+
154+ def encode(self, encodings):
155+ lines = self.lines
156+ ulines = self._ulines
157+ for i in range(2):
158+ if encodings[i] != self._encodings[i]:
159+ self._encodings[i] = encodings[i]
160+ if self.binary:
161+ ulines[i] = lines[i][:]
162+ else:
163+ try:
164+ ulines[i] = [l.decode(encodings[i]) for l in lines[i]]
165+ except UnicodeDecodeError, e:
166+ trace.note('Failed to decode using %s, falling back to latin1', e.encoding)
167+ ulines[i] = [l.decode('latin1') for l in lines[i]]
168+ return ulines
169+
170 class DiffWindow(QBzrWindow):
171
172 def __init__(self, arg_provider, parent=None,
173@@ -459,96 +621,24 @@
174 # tree, e.g. for added/deleted file
175
176 self.processEvents()
177-
178- if parent == (None, None): # filter out TREE_ROOT (?)
179- continue
180-
181- # check for manually deleted files (w/o using bzr rm commands)
182- if kind[1] is None:
183- if versioned == (False, True):
184- # added and missed
185- continue
186- if versioned == (True, True):
187- versioned = (True, False)
188- paths = (paths[0], None)
189-
190- renamed = (parent[0], name[0]) != (parent[1], name[1])
191-
192- dates = [None, None]
193- for ix in range(2):
194- if versioned[ix]:
195- try:
196- dates[ix] = self.trees[ix].get_file_mtime(file_id, paths[ix])
197- except OSError, e:
198- if not renamed or e.errno != errno.ENOENT:
199- raise
200- # If we get ENOENT error then probably we trigger
201- # bug #251532 in bzrlib. Take current time instead
202- dates[ix] = time.time()
203- except FileTimestampUnavailable:
204- # ghosts around us (see Bug #513096)
205- dates[ix] = 0 # using 1970/1/1 instead
206-
207- properties_changed = []
208- if bool(executable[0]) != bool(executable[1]):
209- descr = {True: "+x", False: "-x", None: None}
210- properties_changed.append((descr[executable[0]],
211- descr[executable[1]]))
212-
213- if versioned == (True, False):
214- status = N_('removed')
215- elif versioned == (False, True):
216- status = N_('added')
217- elif renamed and changed_content:
218- status = N_('renamed and modified')
219- elif renamed:
220- status = N_('renamed')
221- else:
222- status = N_('modified')
223- # check filter options
224- if not self.filter_options.check(status):
225- continue
226-
227- if ((versioned[0] != versioned[1] or changed_content)
228- and (kind[0] == 'file' or kind[1] == 'file')):
229- lines = []
230- binary = False
231- for ix, tree in enumerate(self.trees):
232- content = ()
233- if versioned[ix] and kind[ix] == 'file':
234- content = get_file_lines_from_tree(tree, file_id)
235- lines.append(content)
236- binary = binary or is_binary_content(content)
237- self.processEvents()
238- if not binary:
239- if versioned == (True, False):
240- groups = [[('delete', 0, len(lines[0]), 0, 0)]]
241- elif versioned == (False, True):
242- groups = [[('insert', 0, 0, 0, len(lines[1]))]]
243- else:
244- groups = self.difference_groups(lines[0], lines[1])
245- ulines = []
246- for l, encoding in zip(lines, [self.encoding_selector_left.encoding,
247- self.encoding_selector_right.encoding]):
248- try:
249- ulines.append([i.decode(encoding) for i in l])
250- except UnicodeDecodeError, e:
251- trace.note('Failed to decode using %s, falling back to latin1', e.encoding)
252- ulines.append([i.decode('latin1') for i in l])
253- lines = ulines
254- data = ((),())
255- else:
256- groups = []
257- data = [''.join(l) for l in lines]
258- else:
259- binary = False
260- lines = ((),())
261- groups = ()
262- data = ("", "")
263+ di = DiffItem.create(self.trees, file_id, paths, changed_content,
264+ versioned, parent, name, kind, executable,
265+ filter = self.filter_options.check)
266+ if not di:
267+ continue
268+
269+ lines = di.lines
270+ self.processEvents()
271+ groups = di.groups(self.complete, self.ignore_whitespace)
272+ self.processEvents()
273+ ulines = di.encode([self.encoding_selector_left.encoding,
274+ self.encoding_selector_right.encoding])
275+ data = [''.join(l) for l in ulines]
276+
277 for view in self.views:
278- view.append_diff(list(paths), file_id, kind, status,
279- dates, versioned, binary, lines, groups,
280- data, properties_changed)
281+ view.append_diff(list(di.paths), di.file_id, di.kind, di.status,
282+ di.dates, di.versioned, di.binary, ulines, groups,
283+ data, di.properties_changed)
284 self.processEvents()
285 no_changes = False
286 except PathsNotVersionedError, e:
287@@ -565,21 +655,6 @@
288 gettext('&OK'))
289 self.view_refresh.setEnabled(self.can_refresh())
290
291- def difference_groups(self, left, right):
292- if self.ignore_whitespace:
293- table = string.maketrans("", "")
294- strip = lambda l : l.translate(table, string.whitespace)
295- left = (line.translate(table, string.whitespace) for line in left)
296- right = (line.translate(table, string.whitespace) for line in right)
297- matcher = SequenceMatcher(None, left, right)
298- self.processEvents()
299- if self.complete:
300- groups = list([matcher.get_opcodes()])
301- else:
302- groups = list(matcher.get_grouped_opcodes())
303-
304- return groups
305-
306 def click_toggle_view_mode(self, checked):
307 if checked:
308 view = self.sdiffview

Subscribers

People subscribed via source and target branches