~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

Fix ``bzr log -r`` to support selecting merge revisions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
59
59
    symbol_versioning,
60
60
    )
61
61
import bzrlib.errors as errors
 
62
from bzrlib.revisionspec import(
 
63
    RevisionInfo
 
64
    )
62
65
from bzrlib.symbol_versioning import(
63
66
    deprecated_method,
64
67
    zero_eleven,
115
118
        revno += 1
116
119
 
117
120
 
118
 
 
119
121
def _enumerate_history(branch):
120
122
    rh = []
121
123
    revno = 1
161
163
                  start_revision, end_revision, search)
162
164
    finally:
163
165
        branch.unlock()
 
166
 
164
167
    
165
168
def _show_log(branch,
166
169
             lf,
188
191
    else:
189
192
        searchRE = None
190
193
 
191
 
    which_revs = _enumerate_history(branch)
192
 
    
193
 
    if start_revision is None:
194
 
        start_revision = 1
195
 
    else:
196
 
        branch.check_real_revno(start_revision)
197
 
    
198
 
    if end_revision is None:
199
 
        end_revision = len(which_revs)
200
 
    else:
201
 
        branch.check_real_revno(end_revision)
202
 
 
203
 
    # list indexes are 0-based; revisions are 1-based
204
 
    cut_revs = which_revs[(start_revision-1):(end_revision)]
205
 
    if not cut_revs:
 
194
    mainline_revs, rev_nos, start_rev_id, end_rev_id = \
 
195
        _get_mainline_revs(branch, start_revision, end_revision)
 
196
    if not mainline_revs:
206
197
        return
207
198
 
208
 
    # convert the revision history to a dictionary:
209
 
    rev_nos = dict((k, v) for v, k in cut_revs)
210
 
 
211
 
    # override the mainline to look like the revision history.
212
 
    mainline_revs = [revision_id for index, revision_id in cut_revs]
213
 
    if cut_revs[0][0] == 1:
214
 
        mainline_revs.insert(0, None)
215
 
    else:
216
 
        mainline_revs.insert(0, which_revs[start_revision-2][1])
 
199
    if direction == 'reverse':
 
200
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
 
201
        
217
202
    legacy_lf = not getattr(lf,'log_revision',None)
218
203
    if legacy_lf:
219
204
        # pre-0.17 formatters use show for mainline revisions.
236
221
                                           False)
237
222
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
238
223
                          direction, include_merges=generate_merge_revisions)
 
224
    view_revisions = _filter_revision_range(list(view_revs_iter),
 
225
                                            start_rev_id,
 
226
                                            end_rev_id)
239
227
    if specific_fileid:
240
 
        view_revisions = _get_revisions_touching_file_id(branch,
 
228
        view_revisions = _filter_revisions_touching_file_id(branch,
241
229
                                                         specific_fileid,
242
230
                                                         mainline_revs,
243
 
                                                         view_revs_iter)
244
 
    else:
245
 
        view_revisions = list(view_revs_iter)
 
231
                                                         view_revisions)
246
232
 
247
233
    rev_tag_dict = {}
248
234
    generate_tags = getattr(lf, 'supports_tags', False)
310
296
        lf.end_log()
311
297
 
312
298
 
313
 
def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
314
 
                                    view_revs_iter):
 
299
def _get_mainline_revs(branch, start_revision, end_revision):
 
300
    """Get the mainline revisions from the branch.
 
301
    
 
302
    Generates the list of mainline revisions for the branch.
 
303
    
 
304
    :param  branch: The branch containing the revisions. 
 
305
 
 
306
    :param  start_revision: The first revision to be logged.
 
307
            For backwards compatibility this may be a mainline integer revno,
 
308
            but for merge revision support a RevisionInfo is expected.
 
309
 
 
310
    :param  end_revision: The last revision to be logged.
 
311
            For backwards compatibility this may be a mainline integer revno,
 
312
            but for merge revision support a RevisionInfo is expected.
 
313
 
 
314
    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
 
315
    """
 
316
    which_revs = _enumerate_history(branch)
 
317
    if not which_revs:
 
318
        return None, None, None, None
 
319
 
 
320
    # For mainline generation, map start_revision and end_revision to 
 
321
    # mainline revnos. If the revision is not on the mainline choose the 
 
322
    # appropriate extreme of the mainline instead - the extra will be 
 
323
    # filtered later.
 
324
    # Also map the revisions to rev_ids, to be used in the later filtering
 
325
    # stage.
 
326
    start_rev_id = None 
 
327
    if start_revision is None:
 
328
        start_revno = 1
 
329
    else:
 
330
        if isinstance(start_revision,RevisionInfo):
 
331
            start_rev_id = start_revision.rev_id
 
332
            start_revno = start_revision.revno or 1
 
333
        else:
 
334
            branch.check_real_revno(start_revision)
 
335
            start_revno = start_revision
 
336
    
 
337
    end_rev_id = None
 
338
    if end_revision is None:
 
339
        end_revno = len(which_revs)
 
340
    else:
 
341
        if isinstance(end_revision,RevisionInfo):
 
342
            end_rev_id = end_revision.rev_id
 
343
            end_revno = end_revision.revno or len(which_revs)
 
344
        else:
 
345
            branch.check_real_revno(end_revision)
 
346
            end_revno = end_revision
 
347
 
 
348
    if start_revno > end_revno:
 
349
        from bzrlib.errors import BzrCommandError
 
350
        raise BzrCommandError("Start revision must be older than "
 
351
                              "the end revision.")
 
352
 
 
353
    # list indexes are 0-based; revisions are 1-based
 
354
    cut_revs = which_revs[(start_revno-1):(end_revno)]
 
355
    if not cut_revs:
 
356
        return None, None, None, None
 
357
 
 
358
    # convert the revision history to a dictionary:
 
359
    rev_nos = dict((k, v) for v, k in cut_revs)
 
360
 
 
361
    # override the mainline to look like the revision history.
 
362
    mainline_revs = [revision_id for index, revision_id in cut_revs]
 
363
    if cut_revs[0][0] == 1:
 
364
        mainline_revs.insert(0, None)
 
365
    else:
 
366
        mainline_revs.insert(0, which_revs[start_revno-2][1])
 
367
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
 
368
 
 
369
 
 
370
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
 
371
    """Filter view_revisions based on revision ranges.
 
372
 
 
373
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth) 
 
374
            tuples to be filtered.
 
375
 
 
376
    :param start_rev_id: If not NONE specifies the first revision to be logged.
 
377
            If NONE then all revisions up to the end_rev_id are logged.
 
378
 
 
379
    :param end_rev_id: If not NONE specifies the last revision to be logged.
 
380
            If NONE then all revisions up to the end of the log are logged.
 
381
 
 
382
    :return: The filtered view_revisions.
 
383
    """
 
384
    if start_rev_id or end_rev_id: 
 
385
        revision_ids = [r for r, n, d in view_revisions]
 
386
        if start_rev_id:
 
387
            start_index = revision_ids.index(start_rev_id)
 
388
        else:
 
389
            start_index = 0
 
390
        if start_rev_id == end_rev_id:
 
391
            end_index = start_index
 
392
        else:
 
393
            if end_rev_id:
 
394
                end_index = revision_ids.index(end_rev_id)
 
395
            else:
 
396
                end_index = len(view_revisions) - 1
 
397
        # To include the revisions merged into the last revision, 
 
398
        # extend end_rev_id down to, but not including, the next rev
 
399
        # with the same or lesser merge_depth
 
400
        end_merge_depth = view_revisions[end_index][2]
 
401
        try:
 
402
            for index in xrange(end_index+1, len(view_revisions)+1):
 
403
                if view_revisions[index][2] <= end_merge_depth:
 
404
                    end_index = index - 1
 
405
                    break
 
406
        except IndexError:
 
407
            # if the search falls off the end then log to the end as well
 
408
            end_index = len(view_revisions) - 1
 
409
        view_revisions = view_revisions[start_index:end_index+1]
 
410
    return view_revisions
 
411
 
 
412
 
 
413
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
 
414
                                       view_revs_iter):
315
415
    """Return the list of revision ids which touch a given file id.
316
416
 
 
417
    The function filters view_revisions and returns a subset.
317
418
    This includes the revisions which directly change the file id,
318
419
    and the revisions which merge these changes. So if the
319
420
    revision graph is::