48 except IOError, inst: |
48 except IOError, inst: |
49 raise util.Abort(_("can't read commit message '%s': %s") % |
49 raise util.Abort(_("can't read commit message '%s': %s") % |
50 (logfile, inst.strerror)) |
50 (logfile, inst.strerror)) |
51 return message |
51 return message |
52 |
52 |
53 def walkchangerevs(ui, repo, pats, opts): |
53 def walkchangerevs(ui, repo, pats, change, opts): |
54 '''Iterate over files and the revs they changed in. |
54 '''Iterate over files and the revs they changed in. |
55 |
55 |
56 Callers most commonly need to iterate backwards over the history |
56 Callers most commonly need to iterate backwards over the history |
57 it is interested in. Doing so has awful (quadratic-looking) |
57 it is interested in. Doing so has awful (quadratic-looking) |
58 performance, so we use iterators in a "windowed" way. |
58 performance, so we use iterators in a "windowed" way. |
59 |
59 |
60 We walk a window of revisions in the desired order. Within the |
60 We walk a window of revisions in the desired order. Within the |
61 window, we first walk forwards to gather data, then in the desired |
61 window, we first walk forwards to gather data, then in the desired |
62 order (usually backwards) to display it. |
62 order (usually backwards) to display it. |
63 |
63 |
64 This function returns an (iterator, getchange, matchfn) tuple. The |
64 This function returns an (iterator, matchfn) tuple. The iterator |
65 getchange function returns the changelog entry for a numeric |
65 yields 3-tuples. They will be of one of the following forms: |
66 revision. The iterator yields 3-tuples. They will be of one of |
|
67 the following forms: |
|
68 |
66 |
69 "window", incrementing, lastrev: stepping through a window, |
67 "window", incrementing, lastrev: stepping through a window, |
70 positive if walking forwards through revs, last rev in the |
68 positive if walking forwards through revs, last rev in the |
71 sequence iterated over - use to reset state for the current window |
69 sequence iterated over - use to reset state for the current window |
72 |
70 |
89 yield start, min(windowsize, start-end-1) |
87 yield start, min(windowsize, start-end-1) |
90 start -= windowsize |
88 start -= windowsize |
91 if windowsize < sizelimit: |
89 if windowsize < sizelimit: |
92 windowsize *= 2 |
90 windowsize *= 2 |
93 |
91 |
94 |
|
95 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) |
92 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) |
96 follow = opts.get('follow') or opts.get('follow_first') |
93 follow = opts.get('follow') or opts.get('follow_first') |
97 |
94 |
98 if repo.changelog.count() == 0: |
95 if repo.changelog.count() == 0: |
99 return [], False, matchfn |
96 return [], matchfn |
100 |
97 |
101 if follow: |
98 if follow: |
102 defrange = '%s:0' % repo.changectx().rev() |
99 defrange = '%s:0' % repo.changectx().rev() |
103 else: |
100 else: |
104 defrange = 'tip:0' |
101 defrange = 'tip:0' |
105 revs = map(int, cmdutil.revrange(ui, repo, opts['rev'] or [defrange])) |
102 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange]) |
106 wanted = {} |
103 wanted = {} |
107 slowpath = anypats |
104 slowpath = anypats |
108 fncache = {} |
105 fncache = {} |
109 |
106 |
110 chcache = {} |
|
111 def getchange(rev): |
|
112 ch = chcache.get(rev) |
|
113 if ch is None: |
|
114 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev))) |
|
115 return ch |
|
116 |
|
117 if not slowpath and not files: |
107 if not slowpath and not files: |
118 # No files, no patterns. Display all revs. |
108 # No files, no patterns. Display all revs. |
119 wanted = dict(zip(revs, revs)) |
109 wanted = dict.fromkeys(revs) |
120 copies = [] |
110 copies = [] |
121 if not slowpath: |
111 if not slowpath: |
122 # Only files, no patterns. Check the history of each file. |
112 # Only files, no patterns. Check the history of each file. |
123 def filerevgen(filelog, node): |
113 def filerevgen(filelog, node): |
124 cl_count = repo.changelog.count() |
114 cl_count = repo.changelog.count() |
167 |
157 |
168 # The slow path checks files modified in every changeset. |
158 # The slow path checks files modified in every changeset. |
169 def changerevgen(): |
159 def changerevgen(): |
170 for i, window in increasing_windows(repo.changelog.count()-1, -1): |
160 for i, window in increasing_windows(repo.changelog.count()-1, -1): |
171 for j in xrange(i - window, i + 1): |
161 for j in xrange(i - window, i + 1): |
172 yield j, getchange(j)[3] |
162 yield j, change(j)[3] |
173 |
163 |
174 for rev, changefiles in changerevgen(): |
164 for rev, changefiles in changerevgen(): |
175 matches = filter(matchfn, changefiles) |
165 matches = filter(matchfn, changefiles) |
176 if matches: |
166 if matches: |
177 fncache[rev] = matches |
167 fncache[rev] = matches |
218 for rev in opts.get('prune', ()): |
208 for rev in opts.get('prune', ()): |
219 rev = repo.changelog.rev(repo.lookup(rev)) |
209 rev = repo.changelog.rev(repo.lookup(rev)) |
220 ff = followfilter() |
210 ff = followfilter() |
221 stop = min(revs[0], revs[-1]) |
211 stop = min(revs[0], revs[-1]) |
222 for x in xrange(rev, stop-1, -1): |
212 for x in xrange(rev, stop-1, -1): |
223 if ff.match(x) and wanted.has_key(x): |
213 if ff.match(x) and x in wanted: |
224 del wanted[x] |
214 del wanted[x] |
225 |
215 |
226 def iterate(): |
216 def iterate(): |
227 if follow and not files: |
217 if follow and not files: |
228 ff = followfilter(onlyfirst=opts.get('follow_first')) |
218 ff = followfilter(onlyfirst=opts.get('follow_first')) |
238 yield 'window', revs[0] < revs[-1], revs[-1] |
228 yield 'window', revs[0] < revs[-1], revs[-1] |
239 nrevs = [rev for rev in revs[i:i+window] if want(rev)] |
229 nrevs = [rev for rev in revs[i:i+window] if want(rev)] |
240 srevs = list(nrevs) |
230 srevs = list(nrevs) |
241 srevs.sort() |
231 srevs.sort() |
242 for rev in srevs: |
232 for rev in srevs: |
243 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3]) |
233 fns = fncache.get(rev) or filter(matchfn, change(rev)[3]) |
244 yield 'add', rev, fns |
234 yield 'add', rev, fns |
245 for rev in nrevs: |
235 for rev in nrevs: |
246 yield 'iter', rev, None |
236 yield 'iter', rev, None |
247 return iterate(), getchange, matchfn |
237 return iterate(), matchfn |
248 |
238 |
249 def write_bundle(cg, filename=None, compress=True): |
239 def write_bundle(cg, filename=None, compress=True): |
250 """Write a bundle file and return its filename. |
240 """Write a bundle file and return its filename. |
251 |
241 |
252 Existing files will not be overwritten. |
242 Existing files will not be overwritten. |
295 finally: |
285 finally: |
296 if fh is not None: |
286 if fh is not None: |
297 fh.close() |
287 fh.close() |
298 if cleanup is not None: |
288 if cleanup is not None: |
299 os.unlink(cleanup) |
289 os.unlink(cleanup) |
300 |
|
301 def trimuser(ui, name, rev, revcache): |
|
302 """trim the name of the user who committed a change""" |
|
303 user = revcache.get(rev) |
|
304 if user is None: |
|
305 user = revcache[rev] = ui.shortuser(name) |
|
306 return user |
|
307 |
290 |
308 class changeset_printer(object): |
291 class changeset_printer(object): |
309 '''show changeset information when templating not requested.''' |
292 '''show changeset information when templating not requested.''' |
310 |
293 |
311 def __init__(self, ui, repo): |
294 def __init__(self, ui, repo): |
1381 With the --switch-parent option, the diff will be against the second |
1364 With the --switch-parent option, the diff will be against the second |
1382 parent. It can be useful to review a merge. |
1365 parent. It can be useful to review a merge. |
1383 """ |
1366 """ |
1384 if not changesets: |
1367 if not changesets: |
1385 raise util.Abort(_("export requires at least one changeset")) |
1368 raise util.Abort(_("export requires at least one changeset")) |
1386 revs = list(cmdutil.revrange(ui, repo, changesets)) |
1369 revs = cmdutil.revrange(ui, repo, changesets) |
1387 if len(revs) > 1: |
1370 if len(revs) > 1: |
1388 ui.note(_('exporting patches:\n')) |
1371 ui.note(_('exporting patches:\n')) |
1389 else: |
1372 else: |
1390 ui.note(_('exporting patch:\n')) |
1373 ui.note(_('exporting patch:\n')) |
1391 patch.export(repo, map(repo.lookup, revs), template=opts['output'], |
1374 patch.export(repo, map(repo.lookup, revs), template=opts['output'], |
1469 yield ('-', a[i]) |
1452 yield ('-', a[i]) |
1470 for i in xrange(blo, bhi): |
1453 for i in xrange(blo, bhi): |
1471 yield ('+', b[i]) |
1454 yield ('+', b[i]) |
1472 |
1455 |
1473 prev = {} |
1456 prev = {} |
1474 ucache = {} |
|
1475 def display(fn, rev, states, prevstates): |
1457 def display(fn, rev, states, prevstates): |
1476 counts = {'-': 0, '+': 0} |
1458 counts = {'-': 0, '+': 0} |
1477 filerevmatches = {} |
1459 filerevmatches = {} |
1478 if incrementing or not opts['all']: |
1460 if incrementing or not opts['all']: |
1479 a, b = prevstates, states |
1461 a, b, r = prevstates, states, rev |
1480 else: |
1462 else: |
1481 a, b = states, prevstates |
1463 a, b, r = states, prevstates, prev.get(fn, -1) |
1482 for change, l in difflinestates(a, b): |
1464 for change, l in difflinestates(a, b): |
1483 if incrementing or not opts['all']: |
|
1484 r = rev |
|
1485 else: |
|
1486 r = prev[fn] |
|
1487 cols = [fn, str(r)] |
1465 cols = [fn, str(r)] |
1488 if opts['line_number']: |
1466 if opts['line_number']: |
1489 cols.append(str(l.linenum)) |
1467 cols.append(str(l.linenum)) |
1490 if opts['all']: |
1468 if opts['all']: |
1491 cols.append(change) |
1469 cols.append(change) |
1492 if opts['user']: |
1470 if opts['user']: |
1493 cols.append(trimuser(ui, getchange(r)[1], rev, |
1471 cols.append(ui.shortuser(getchange(r)[1])) |
1494 ucache)) |
|
1495 if opts['files_with_matches']: |
1472 if opts['files_with_matches']: |
1496 c = (fn, rev) |
1473 c = (fn, r) |
1497 if c in filerevmatches: |
1474 if c in filerevmatches: |
1498 continue |
1475 continue |
1499 filerevmatches[c] = 1 |
1476 filerevmatches[c] = 1 |
1500 else: |
1477 else: |
1501 cols.append(l.line) |
1478 cols.append(l.line) |
1503 counts[change] += 1 |
1480 counts[change] += 1 |
1504 return counts['+'], counts['-'] |
1481 return counts['+'], counts['-'] |
1505 |
1482 |
1506 fstate = {} |
1483 fstate = {} |
1507 skip = {} |
1484 skip = {} |
1508 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts) |
1485 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset()) |
|
1486 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts) |
1509 count = 0 |
1487 count = 0 |
1510 incrementing = False |
1488 incrementing = False |
1511 follow = opts.get('follow') |
1489 follow = opts.get('follow') |
1512 for st, rev, fns in changeiter: |
1490 for st, rev, fns in changeiter: |
1513 if st == 'window': |
1491 if st == 'window': |
1514 incrementing = rev |
1492 incrementing = rev |
1515 matches.clear() |
1493 matches.clear() |
1516 elif st == 'add': |
1494 elif st == 'add': |
1517 change = repo.changelog.read(repo.lookup(str(rev))) |
1495 mf = repo.changectx(rev).manifest() |
1518 mf = repo.manifest.read(change[0]) |
|
1519 matches[rev] = {} |
1496 matches[rev] = {} |
1520 for fn in fns: |
1497 for fn in fns: |
1521 if fn in skip: |
1498 if fn in skip: |
1522 continue |
1499 continue |
1523 fstate.setdefault(fn, {}) |
1500 fstate.setdefault(fn, {}) |
1836 if self.debugflag: |
1813 if self.debugflag: |
1837 self.write(*args) |
1814 self.write(*args) |
1838 def __getattr__(self, key): |
1815 def __getattr__(self, key): |
1839 return getattr(self.ui, key) |
1816 return getattr(self.ui, key) |
1840 |
1817 |
1841 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts) |
1818 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset()) |
|
1819 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts) |
1842 |
1820 |
1843 if opts['branches']: |
1821 if opts['branches']: |
1844 ui.warn(_("the --branches option is deprecated, " |
1822 ui.warn(_("the --branches option is deprecated, " |
1845 "please use 'hg branches' instead\n")) |
1823 "please use 'hg branches' instead\n")) |
1846 |
1824 |