39
36
from StringIO import StringIO
41
from loggerhead import search
42
38
from loggerhead import util
43
from loggerhead.wholehistory import compute_whole_history_data
39
from loggerhead.util import decorator
46
42
import bzrlib.branch
43
import bzrlib.bundle.serializer
49
45
import bzrlib.errors
50
46
import bzrlib.progress
51
47
import bzrlib.revision
52
import bzrlib.textfile
53
48
import bzrlib.tsort
52
with_branch_lock = util.with_lock('_lock', 'branch')
56
def with_bzrlib_read_lock(unbound):
57
def bzrlib_read_locked(self, *args, **kw):
58
#self.log.debug('-> %r bzr lock', id(threading.currentThread()))
59
self._branch.repository.lock_read()
61
return unbound(self, *args, **kw)
63
self._branch.repository.unlock()
64
#self.log.debug('<- %r bzr lock', id(threading.currentThread()))
65
return bzrlib_read_locked
56
68
# bzrlib's UIFactory is not thread-safe
57
69
uihack = threading.local()
60
71
class ThreadSafeUIFactory (bzrlib.ui.SilentUIFactory):
62
72
def nested_progress_bar(self):
63
73
if getattr(uihack, '_progress_bar_stack', None) is None:
64
pbs = bzrlib.progress.ProgressBarStack(
65
klass=bzrlib.progress.DummyProgress)
66
uihack._progress_bar_stack = pbs
74
uihack._progress_bar_stack = bzrlib.progress.ProgressBarStack(klass=bzrlib.progress.DummyProgress)
67
75
return uihack._progress_bar_stack.get_nested()
69
77
bzrlib.ui.ui_factory = ThreadSafeUIFactory()
80
def _process_side_by_side_buffers(line_list, delete_list, insert_list):
81
while len(delete_list) < len(insert_list):
82
delete_list.append((None, '', 'context'))
83
while len(insert_list) < len(delete_list):
84
insert_list.append((None, '', 'context'))
85
while len(delete_list) > 0:
86
d = delete_list.pop(0)
87
i = insert_list.pop(0)
88
line_list.append(util.Container(old_lineno=d[0], new_lineno=i[0],
89
old_line=d[1], new_line=i[1],
90
old_type=d[2], new_type=i[2]))
93
def _make_side_by_side(chunk_list):
95
turn a normal unified-style diff (post-processed by parse_delta) into a
96
side-by-side diff structure. the new structure is::
104
type: str('context' or 'changed'),
109
for chunk in chunk_list:
111
delete_list, insert_list = [], []
112
for line in chunk.diff:
113
if line.type == 'context':
114
if len(delete_list) or len(insert_list):
115
_process_side_by_side_buffers(line_list, delete_list, insert_list)
116
delete_list, insert_list = [], []
117
line_list.append(util.Container(old_lineno=line.old_lineno, new_lineno=line.new_lineno,
118
old_line=line.line, new_line=line.line,
119
old_type=line.type, new_type=line.type))
120
elif line.type == 'delete':
121
delete_list.append((line.old_lineno, line.line, line.type))
122
elif line.type == 'insert':
123
insert_list.append((line.new_lineno, line.line, line.type))
124
if len(delete_list) or len(insert_list):
125
_process_side_by_side_buffers(line_list, delete_list, insert_list)
126
out_chunk_list.append(util.Container(diff=line_list))
127
return out_chunk_list
71
130
def is_branch(folder):
73
132
bzrlib.branch.Branch.open(folder)
125
183
def __getitem__(self, index):
126
184
"""Get the date of the index'd item"""
127
return datetime.datetime.fromtimestamp(self.repository.get_revision(
128
self.revid_list[index]).timestamp)
185
return datetime.datetime.fromtimestamp(self.repository.get_revision(self.revid_list[index]).timestamp)
130
187
def __len__(self):
131
188
return len(self.revid_list)
133
class FileChangeReporter(object):
134
def __init__(self, old_inv, new_inv):
139
self.text_changes = []
140
self.old_inv = old_inv
141
self.new_inv = new_inv
143
def revid(self, inv, file_id):
145
return inv[file_id].revision
146
except bzrlib.errors.NoSuchId:
149
def report(self, file_id, paths, versioned, renamed, modified,
151
if modified not in ('unchanged', 'kind changed'):
152
if versioned == 'removed':
153
filename = rich_filename(paths[0], kind[0])
155
filename = rich_filename(paths[1], kind[1])
156
self.text_changes.append(util.Container(
157
filename=filename, file_id=file_id,
158
old_revision=self.revid(self.old_inv, file_id),
159
new_revision=self.revid(self.new_inv, file_id)))
160
if versioned == 'added':
161
self.added.append(util.Container(
162
filename=rich_filename(paths[1], kind),
163
file_id=file_id, kind=kind[1]))
164
elif versioned == 'removed':
165
self.removed.append(util.Container(
166
filename=rich_filename(paths[0], kind),
167
file_id=file_id, kind=kind[0]))
169
self.renamed.append(util.Container(
170
old_filename=rich_filename(paths[0], kind[0]),
171
new_filename=rich_filename(paths[1], kind[1]),
173
text_modified=modified == 'modified'))
175
self.modified.append(util.Container(
176
filename=rich_filename(paths[1], kind),
180
191
class History (object):
181
"""Decorate a branch to provide information for rendering.
183
History objects are expected to be short lived -- when serving a request
184
for a particular branch, open it, read-lock it, wrap a History object
185
around it, serve the request, throw the History object away, unlock the
186
branch and throw it away.
188
:ivar _file_change_cache: xx
191
def __init__(self, branch, whole_history_data_cache):
192
assert branch.is_locked(), (
193
"Can only construct a History object with a read-locked branch.")
194
self._change_cache = None
194
195
self._file_change_cache = None
197
self._lock = threading.RLock()
200
def from_branch(cls, branch, name=None):
195
203
self._branch = branch
196
self._inventory_cache = {}
197
self._branch_nick = self._branch.get_config().get_nickname()
198
self.log = logging.getLogger('loggerhead.%s' % self._branch_nick)
200
self.last_revid = branch.last_revision()
202
whole_history_data = whole_history_data_cache.get(self.last_revid)
203
if whole_history_data is None:
204
whole_history_data = compute_whole_history_data(branch)
205
whole_history_data_cache[self.last_revid] = whole_history_data
207
(self._revision_graph, self._full_history, self._revision_info,
208
self._revno_revid, self._merge_sort, self._where_merged,
209
) = whole_history_data
204
self._last_revid = self._branch.last_revision()
207
name = self._branch.nick
209
self.log = logging.getLogger('loggerhead.%s' % (name,))
211
graph = branch.repository.get_graph()
212
parent_map = dict(((key, value) for key, value in
213
graph.iter_ancestry([self._last_revid]) if value is not None))
215
self._revision_graph = self._strip_NULL_ghosts(parent_map)
216
self._full_history = []
217
self._revision_info = {}
218
self._revno_revid = {}
219
if bzrlib.revision.is_null(self._last_revid):
220
self._merge_sort = []
222
self._merge_sort = bzrlib.tsort.merge_sort(
223
self._revision_graph, self._last_revid, generate_revno=True)
225
for (seq, revid, merge_depth, revno, end_of_merge) in self._merge_sort:
226
self._full_history.append(revid)
227
revno_str = '.'.join(str(n) for n in revno)
228
self._revno_revid[revno_str] = revid
229
self._revision_info[revid] = (
230
seq, revid, merge_depth, revno_str, end_of_merge)
233
self._where_merged = {}
235
for revid in self._revision_graph.keys():
236
if self._revision_info[revid][2] == 0:
238
for parent in self._revision_graph[revid]:
239
self._where_merged.setdefault(parent, set()).add(revid)
241
self.log.info('built revision graph cache: %r secs' % (time.time() - z,))
245
def _strip_NULL_ghosts(revision_graph):
247
Copied over from bzrlib meant as a temporary workaround deprecated
251
# Filter ghosts, and null:
252
if bzrlib.revision.NULL_REVISION in revision_graph:
253
del revision_graph[bzrlib.revision.NULL_REVISION]
254
for key, parents in revision_graph.items():
255
revision_graph[key] = tuple(parent for parent in parents if parent
257
return revision_graph
260
def from_folder(cls, path, name=None):
261
b = bzrlib.branch.Branch.open(path)
264
return cls.from_branch(b, name)
269
def out_of_date(self):
270
# the branch may have been upgraded on disk, in which case we're stale.
271
newly_opened = bzrlib.branch.Branch.open(self._branch.base)
272
if self._branch.__class__ is not \
273
newly_opened.__class__:
275
if self._branch.repository.__class__ is not \
276
newly_opened.repository.__class__:
278
return self._branch.last_revision() != self._last_revid
280
def use_cache(self, cache):
281
self._change_cache = cache
211
283
def use_file_cache(self, cache):
212
284
self._file_change_cache = cache
286
def use_search_index(self, index):
215
290
def has_revisions(self):
216
291
return not bzrlib.revision.is_null(self.last_revid)
295
# called when a new history object needs to be created, because the
296
# branch history has changed. we need to immediately close and stop
297
# using our caches, because a new history object will be created to
298
# replace us, using the same cache files.
299
# (may also be called during server shutdown.)
300
if self._change_cache is not None:
301
self._change_cache.close()
302
self._change_cache = None
303
if self._index is not None:
307
def flush_cache(self):
308
if self._change_cache is None:
310
self._change_cache.flush()
312
def check_rebuild(self):
313
if self._change_cache is not None:
314
self._change_cache.check_rebuild()
315
#if self._index is not None:
316
# self._index.check_rebuild()
318
last_revid = property(lambda self: self._last_revid, None, None)
218
321
def get_config(self):
219
322
return self._branch.get_config()
221
325
def get_revno(self, revid):
222
326
if revid not in self._revision_info:
225
(seq, revid, merge_depth,
226
revno_str, end_of_merge) = self._revision_info[revid]
329
seq, revid, merge_depth, revno_str, end_of_merge = self._revision_info[revid]
332
def get_revision_history(self):
333
return self._full_history
229
335
def get_revids_from(self, revid_list, start_revid):
231
337
Yield the mainline (wrt start_revid) revisions that merged each
255
360
revid = parents[0]
257
363
def get_short_revision_history_by_fileid(self, file_id):
364
# wow. is this really the only way we can get this list? by
365
# man-handling the weave store directly? :-0
258
366
# FIXME: would be awesome if we could get, for a folder, the list of
259
# revisions where items within that folder changed.i
261
# FIXME: Workaround for bzr versions prior to 1.6b3.
262
# Remove me eventually pretty please :)
263
w = self._branch.repository.weave_store.get_weave(
264
file_id, self._branch.repository.get_transaction())
265
w_revids = w.versions()
266
revids = [r for r in self._full_history if r in w_revids]
267
except AttributeError:
268
possible_keys = [(file_id, revid) for revid in self._full_history]
269
get_parent_map = self._branch.repository.texts.get_parent_map
270
# We chunk the requests as this works better with GraphIndex.
271
# See _filter_revisions_touching_file_id in bzrlib/log.py
272
# for more information.
275
for start in xrange(0, len(possible_keys), chunk_size):
276
next_keys = possible_keys[start:start + chunk_size]
277
revids += [k[1] for k in get_parent_map(next_keys)]
278
del possible_keys, next_keys
367
# revisions where items within that folder changed.
368
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
369
w_revids = w.versions()
370
revids = [r for r in self._full_history if r in w_revids]
281
374
def get_revision_history_since(self, revid_list, date):
282
375
# if a user asks for revisions starting at 01-sep, they mean inclusive,
283
376
# so start at midnight on 02-sep.
284
377
date = date + datetime.timedelta(days=1)
285
# our revid list is sorted in REVERSE date order,
286
# so go thru some hoops here...
378
# our revid list is sorted in REVERSE date order, so go thru some hoops here...
287
379
revid_list.reverse()
288
index = bisect.bisect(_RevListToTimestamps(revid_list,
289
self._branch.repository),
380
index = bisect.bisect(_RevListToTimestamps(revid_list, self._branch.repository), date)
293
383
revid_list.reverse()
295
385
return revid_list[index:]
388
def get_revision_history_matching(self, revid_list, text):
389
self.log.debug('searching %d revisions for %r', len(revid_list), text)
391
# this is going to be painfully slow. :(
394
for revid in revid_list:
395
change = self.get_changes([ revid ])[0]
396
if text in change.comment.lower():
398
self.log.debug('searched %d revisions for %r in %r secs', len(revid_list), text, time.time() - z)
401
def get_revision_history_matching_indexed(self, revid_list, text):
402
self.log.debug('searching %d revisions for %r', len(revid_list), text)
404
if self._index is None:
405
return self.get_revision_history_matching(revid_list, text)
406
out = self._index.find(text, revid_list)
407
self.log.debug('searched %d revisions for %r in %r secs: %d results', len(revid_list), text, time.time() - z, len(out))
408
# put them in some coherent order :)
409
out = [r for r in self._full_history if r in out]
297
413
def get_search_revid_list(self, query, revid_list):
299
415
given a "quick-search" query, try a few obvious possible meanings:
301
417
- revision id or # ("128.1.3")
302
- date (US style "mm/dd/yy", earth style "dd-mm-yy", or \
303
iso style "yyyy-mm-dd")
418
- date (US style "mm/dd/yy", earth style "dd-mm-yy", or iso style "yyyy-mm-dd")
304
419
- comment text as a fallback
306
421
and return a revid list that matches.
309
424
# all the relevant changes (time-consuming) only to return a list of
310
425
# revids which will be used to fetch a set of changes again.
312
# if they entered a revid, just jump straight there;
313
# ignore the passed-in revid_list
427
# if they entered a revid, just jump straight there; ignore the passed-in revid_list
314
428
revid = self.fix_revid(query)
315
429
if revid is not None:
316
430
if isinstance(revid, unicode):
317
431
revid = revid.encode('utf-8')
318
changes = self.get_changes([revid])
432
changes = self.get_changes([ revid ])
319
433
if (changes is not None) and (len(changes) > 0):
323
437
m = self.us_date_re.match(query)
324
438
if m is not None:
325
date = datetime.datetime(util.fix_year(int(m.group(3))),
439
date = datetime.datetime(util.fix_year(int(m.group(3))), int(m.group(1)), int(m.group(2)))
329
441
m = self.earth_date_re.match(query)
330
442
if m is not None:
331
date = datetime.datetime(util.fix_year(int(m.group(3))),
443
date = datetime.datetime(util.fix_year(int(m.group(3))), int(m.group(2)), int(m.group(1)))
335
445
m = self.iso_date_re.match(query)
336
446
if m is not None:
337
date = datetime.datetime(util.fix_year(int(m.group(1))),
447
date = datetime.datetime(util.fix_year(int(m.group(1))), int(m.group(2)), int(m.group(3)))
340
448
if date is not None:
341
449
if revid_list is None:
342
# if no limit to the query was given,
343
# search only the direct-parent path.
344
revid_list = list(self.get_revids_from(None, self.last_revid))
450
# if no limit to the query was given, search only the direct-parent path.
451
revid_list = list(self.get_revids_from(None, self._last_revid))
345
452
return self.get_revision_history_since(revid_list, date)
454
# check comment fields.
455
if revid_list is None:
456
revid_list = self._full_history
457
return self.get_revision_history_matching_indexed(revid_list, query)
347
459
revno_re = re.compile(r'^[\d\.]+$')
348
460
# the date regex are without a final '$' so that queries like
349
461
# "2006-11-30 12:15" still mostly work. (i think it's better to give
427
538
revid_list = self.get_file_view(start_revid, file_id)
429
540
revid_list = None
430
revid_list = search.search_revisions(self._branch, query)
431
if revid_list and len(revid_list) > 0:
542
revid_list = self.get_search_revid_list(query, revid_list)
543
if len(revid_list) > 0:
432
544
if revid not in revid_list:
433
545
revid = revid_list[0]
434
546
return revid, start_revid, revid_list
436
# XXX: This should return a message saying that the search could
437
# not be completed due to either missing the plugin or missing a
439
549
return None, None, []
441
552
def get_inventory(self, revid):
442
if revid not in self._inventory_cache:
443
self._inventory_cache[revid] = (
444
self._branch.repository.get_revision_inventory(revid))
445
return self._inventory_cache[revid]
553
return self._branch.repository.get_revision_inventory(revid)
447
556
def get_path(self, revid, file_id):
448
557
if (file_id is None) or (file_id == ''):
450
path = self.get_inventory(revid).id2path(file_id)
559
path = self._branch.repository.get_revision_inventory(revid).id2path(file_id)
451
560
if (len(path) > 0) and not path.startswith('/'):
452
561
path = '/' + path
455
565
def get_file_id(self, revid, path):
456
566
if (len(path) > 0) and not path.startswith('/'):
457
567
path = '/' + path
458
return self.get_inventory(revid).path2id(path)
568
return self._branch.repository.get_revision_inventory(revid).path2id(path)
460
571
def get_merge_point_list(self, revid):
495
606
revnol = revno.split(".")
496
607
revnos = ".".join(revnol[:-2])
497
608
revnolast = int(revnol[-1])
498
if revnos in d.keys():
609
if d.has_key(revnos):
500
611
if revnolast < m:
501
d[revnos] = (revnolast, revid)
612
d[revnos] = ( revnolast, revid )
503
d[revnos] = (revnolast, revid)
505
return [d[revnos][1] for revnos in d.keys()]
507
def add_branch_nicks(self, change):
614
d[revnos] = ( revnolast, revid )
616
return [ d[revnos][1] for revnos in d.keys() ]
618
def get_branch_nicks(self, changes):
509
given a 'change', fill in the branch nicks on all parents and merge
620
given a list of changes from L{get_changes}, fill in the branch nicks
621
on all parents and merge points.
512
623
fetch_set = set()
513
for p in change.parents:
514
fetch_set.add(p.revid)
515
for p in change.merge_points:
516
fetch_set.add(p.revid)
624
for change in changes:
625
for p in change.parents:
626
fetch_set.add(p.revid)
627
for p in change.merge_points:
628
fetch_set.add(p.revid)
517
629
p_changes = self.get_changes(list(fetch_set))
518
630
p_change_dict = dict([(c.revid, c) for c in p_changes])
519
for p in change.parents:
520
if p.revid in p_change_dict:
521
p.branch_nick = p_change_dict[p.revid].branch_nick
523
p.branch_nick = '(missing)'
524
for p in change.merge_points:
525
if p.revid in p_change_dict:
526
p.branch_nick = p_change_dict[p.revid].branch_nick
528
p.branch_nick = '(missing)'
631
for change in changes:
632
# arch-converted branches may not have merged branch info :(
633
for p in change.parents:
634
if p.revid in p_change_dict:
635
p.branch_nick = p_change_dict[p.revid].branch_nick
637
p.branch_nick = '(missing)'
638
for p in change.merge_points:
639
if p.revid in p_change_dict:
640
p.branch_nick = p_change_dict[p.revid].branch_nick
642
p.branch_nick = '(missing)'
530
645
def get_changes(self, revid_list):
531
646
"""Return a list of changes objects for the given revids.
533
648
Revisions not present and NULL_REVISION will be ignored.
535
changes = self.get_changes_uncached(revid_list)
650
if self._change_cache is None:
651
changes = self.get_changes_uncached(revid_list)
653
changes = self._change_cache.get_changes(revid_list)
536
654
if len(changes) == 0:
539
657
# some data needs to be recalculated each time, because it may
540
658
# change as new revisions are added.
541
659
for change in changes:
542
merge_revids = self.simplify_merge_point_list(
543
self.get_merge_point_list(change.revid))
544
change.merge_points = [
545
util.Container(revid=r,
546
revno=self.get_revno(r)) for r in merge_revids]
660
merge_revids = self.simplify_merge_point_list(self.get_merge_point_list(change.revid))
661
change.merge_points = [util.Container(revid=r, revno=self.get_revno(r)) for r in merge_revids]
547
662
if len(change.parents) > 0:
548
change.parents = [util.Container(revid=r,
549
revno=self.get_revno(r)) for r in change.parents]
663
if isinstance(change.parents[0], util.Container):
664
# old cache stored a potentially-bogus revno
665
change.parents = [util.Container(revid=p.revid, revno=self.get_revno(p.revid)) for p in change.parents]
667
change.parents = [util.Container(revid=r, revno=self.get_revno(r)) for r in change.parents]
550
668
change.revno = self.get_revno(change.revid)
677
# alright, let's profile this sucka. (FIXME remove this eventually...)
678
def _get_changes_profiled(self, revid_list):
679
from loggerhead.lsprof import profile
681
ret, stats = profile(self.get_changes_uncached, revid_list)
684
cPickle.dump(stats, open('lsprof.stats', 'w'), 2)
685
self.log.info('lsprof complete!')
689
@with_bzrlib_read_lock
559
690
def get_changes_uncached(self, revid_list):
560
# FIXME: deprecated method in getting a null revision
561
691
revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
563
parent_map = self._branch.repository.get_graph().get_parent_map(
693
repo = self._branch.repository
694
parent_map = repo.get_graph().get_parent_map(revid_list)
565
695
# We need to return the answer in the same order as the input,
566
696
# less any ghosts.
567
697
present_revids = [revid for revid in revid_list
568
698
if revid in parent_map]
569
rev_list = self._branch.repository.get_revisions(present_revids)
699
rev_list = repo.get_revisions(present_revids)
571
701
return [self._change_from_revision(rev) for rev in rev_list]
703
def _get_deltas_for_revisions_with_trees(self, revisions):
704
"""Produce a list of revision deltas.
706
Note that the input is a sequence of REVISIONS, not revision_ids.
707
Trees will be held in memory until the generator exits.
708
Each delta is relative to the revision's lefthand predecessor.
709
(This is copied from bzrlib.)
711
required_trees = set()
712
for revision in revisions:
713
required_trees.add(revision.revid)
714
required_trees.update([p.revid for p in revision.parents[:1]])
715
trees = dict((t.get_revision_id(), t) for
716
t in self._branch.repository.revision_trees(required_trees))
718
self._branch.repository.lock_read()
720
for revision in revisions:
721
if not revision.parents:
722
old_tree = self._branch.repository.revision_tree(
723
bzrlib.revision.NULL_REVISION)
725
old_tree = trees[revision.parents[0].revid]
726
tree = trees[revision.revid]
727
ret.append(tree.changes_from(old_tree))
730
self._branch.repository.unlock()
573
732
def _change_from_revision(self, revision):
575
734
Given a bzrlib Revision, return a processed "change" for use in
600
753
return util.Container(entry)
602
def get_file_changes_uncached(self, entry):
603
repo = self._branch.repository
605
old_revid = entry.parents[0].revid
607
old_revid = bzrlib.revision.NULL_REVISION
608
return self.file_changes_for_revision_ids(old_revid, entry.revid)
610
def get_file_changes(self, entry):
755
def get_file_changes_uncached(self, entries):
756
delta_list = self._get_deltas_for_revisions_with_trees(entries)
758
return [self.parse_delta(delta) for delta in delta_list]
761
def get_file_changes(self, entries):
611
762
if self._file_change_cache is None:
612
return self.get_file_changes_uncached(entry)
763
return self.get_file_changes_uncached(entries)
614
return self._file_change_cache.get_file_changes(entry)
616
def add_changes(self, entry):
617
changes = self.get_file_changes(entry)
618
entry.changes = changes
765
return self._file_change_cache.get_file_changes(entries)
767
def add_changes(self, entries):
768
changes_list = self.get_file_changes(entries)
770
for entry, changes in zip(entries, changes_list):
771
entry.changes = changes
774
def get_change_with_diff(self, revid, compare_revid=None):
775
change = self.get_changes([revid])[0]
777
if compare_revid is None:
779
compare_revid = change.parents[0].revid
781
compare_revid = 'null:'
783
rev_tree1 = self._branch.repository.revision_tree(compare_revid)
784
rev_tree2 = self._branch.repository.revision_tree(revid)
785
delta = rev_tree2.changes_from(rev_tree1)
787
change.changes = self.parse_delta(delta)
788
change.changes.modified = self._parse_diffs(rev_tree1, rev_tree2, delta)
620
793
def get_file(self, file_id, revid):
621
794
"returns (path, filename, data)"
622
795
inv = self.get_inventory(revid)
627
800
path = '/' + path
628
801
return path, inv_entry.name, rev_tree.get_file_text(file_id)
630
def file_changes_for_revision_ids(self, old_revid, new_revid):
803
def _parse_diffs(self, old_tree, new_tree, delta):
805
Return a list of processed diffs, in the format::
814
type: str('context', 'delete', or 'insert'),
823
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
825
process.append((old_path, new_path, fid, kind))
826
for path, fid, kind, text_modified, meta_modified in delta.modified:
827
process.append((path, path, fid, kind))
829
for old_path, new_path, fid, kind in process:
830
old_lines = old_tree.get_file_lines(fid)
831
new_lines = new_tree.get_file_lines(fid)
833
if old_lines != new_lines:
835
bzrlib.diff.internal_diff(old_path, old_lines,
836
new_path, new_lines, buffer)
837
except bzrlib.errors.BinaryFile:
840
diff = buffer.getvalue()
843
out.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=self._process_diff(diff), raw_diff=diff))
847
def _process_diff(self, diff):
848
# doesn't really need to be a method; could be static.
851
for line in diff.splitlines():
854
if line.startswith('+++ ') or line.startswith('--- '):
856
if line.startswith('@@ '):
858
if chunk is not None:
860
chunk = util.Container()
862
lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
863
old_lineno = lines[0]
864
new_lineno = lines[1]
865
elif line.startswith(' '):
866
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
867
type='context', line=util.fixed_width(line[1:])))
870
elif line.startswith('+'):
871
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
872
type='insert', line=util.fixed_width(line[1:])))
874
elif line.startswith('-'):
875
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
876
type='delete', line=util.fixed_width(line[1:])))
879
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
880
type='unknown', line=util.fixed_width(repr(line))))
881
if chunk is not None:
885
def parse_delta(self, delta):
632
887
Return a nested data structure containing the changes in a delta::
641
text_changes: list((filename, file_id)),
643
repo = self._branch.repository
644
if bzrlib.revision.is_null(old_revid) or \
645
bzrlib.revision.is_null(new_revid):
646
old_tree, new_tree = map(
647
repo.revision_tree, [old_revid, new_revid])
649
old_tree, new_tree = repo.revision_trees([old_revid, new_revid])
651
reporter = FileChangeReporter(old_tree.inventory, new_tree.inventory)
653
bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
655
return util.Container(
656
added=sorted(reporter.added, key=lambda x:x.filename),
657
renamed=sorted(reporter.renamed, key=lambda x:x.new_filename),
658
removed=sorted(reporter.removed, key=lambda x:x.filename),
659
modified=sorted(reporter.modified, key=lambda x:x.filename),
660
text_changes=sorted(reporter.text_changes, key=lambda x:x.filename))
902
for path, fid, kind in delta.added:
903
added.append((rich_filename(path, kind), fid))
905
for path, fid, kind, text_modified, meta_modified in delta.modified:
906
modified.append(util.Container(filename=rich_filename(path, kind), file_id=fid))
908
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
909
renamed.append((rich_filename(old_path, kind), rich_filename(new_path, kind), fid))
910
if meta_modified or text_modified:
911
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
913
for path, fid, kind in delta.removed:
914
removed.append((rich_filename(path, kind), fid))
916
return util.Container(added=added, renamed=renamed, removed=removed, modified=modified)
919
def add_side_by_side(changes):
920
# FIXME: this is a rotten API.
921
for change in changes:
922
for m in change.changes.modified:
923
m.sbs_chunks = _make_side_by_side(m.chunks)
926
def get_filelist(self, inv, file_id, sort_type=None):
928
return the list of all files (and their attributes) within a given
932
dir_ie = inv[file_id]
933
path = inv.id2path(file_id)
938
for filename, entry in dir_ie.children.iteritems():
939
revid_set.add(entry.revision)
942
for change in self.get_changes(list(revid_set)):
943
change_dict[change.revid] = change
945
for filename, entry in dir_ie.children.iteritems():
947
if entry.kind == 'directory':
950
revid = entry.revision
952
file = util.Container(
953
filename=filename, executable=entry.executable, kind=entry.kind,
954
pathname=pathname, file_id=entry.file_id, size=entry.text_size,
955
revid=revid, change=change_dict[revid])
956
file_list.append(file)
958
if sort_type == 'filename' or sort_type is None:
959
file_list.sort(key=lambda x: x.filename)
960
elif sort_type == 'size':
961
file_list.sort(key=lambda x: x.size)
962
elif sort_type == 'date':
963
file_list.sort(key=lambda x: x.change.date)
966
for file in file_list:
973
_BADCHARS_RE = re.compile(ur'[\x00-\x08\x0b\x0e-\x1f]')
976
def annotate_file(self, file_id, revid):
981
file_revid = self.get_inventory(revid)[file_id].revision
983
tree = self._branch.repository.revision_tree(file_revid)
986
for line_revid, text in tree.annotate_iter(file_id):
987
revid_set.add(line_revid)
988
if self._BADCHARS_RE.match(text):
989
# bail out; this isn't displayable text
990
yield util.Container(parity=0, lineno=1, status='same',
991
text='(This is a binary file.)',
992
change=util.Container())
994
change_cache = dict([(c.revid, c) \
995
for c in self.get_changes(list(revid_set))])
997
last_line_revid = None
998
for line_revid, text in tree.annotate_iter(file_id):
999
if line_revid == last_line_revid:
1000
# remember which lines have a new revno and which don't
1005
last_line_revid = line_revid
1006
change = change_cache[line_revid]
1007
trunc_revno = change.revno
1008
if len(trunc_revno) > 10:
1009
trunc_revno = trunc_revno[:9] + '...'
1011
yield util.Container(parity=parity, lineno=lineno, status=status,
1012
change=change, text=util.fixed_width(text))
1015
self.log.debug('annotate: %r secs' % (time.time() - z,))
1018
def get_bundle(self, revid, compare_revid=None):
1019
if compare_revid is None:
1020
parents = self._revision_graph[revid]
1021
if len(parents) > 0:
1022
compare_revid = parents[0]
1024
compare_revid = None
1026
bzrlib.bundle.serializer.write_bundle(self._branch.repository, revid, compare_revid, s)