##// END OF EJS Templates
merge with stable
Matt Mackall -
r15513:64675914 merge default
parent child Browse files
Show More
@@ -0,0 +1,114 b''
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
3
4 $ tipparents() {
5 > hg parents --template "{rev}:{node|short} {desc|firstline}\n" -r tip
6 > }
7
8 Test import and merge diffs
9
10 $ hg init repo
11 $ cd repo
12 $ echo a > a
13 $ hg ci -Am adda
14 adding a
15 $ echo a >> a
16 $ hg ci -m changea
17 $ echo c > c
18 $ hg ci -Am addc
19 adding c
20 $ hg up 0
21 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 $ echo b > b
23 $ hg ci -Am addb
24 adding b
25 created new head
26 $ hg up 1
27 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
28 $ hg merge 3
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 (branch merge, don't forget to commit)
31 $ hg ci -m merge
32 $ hg export . > ../merge.diff
33 $ cd ..
34 $ hg clone -r2 repo repo2
35 adding changesets
36 adding manifests
37 adding file changes
38 added 3 changesets with 3 changes to 2 files
39 updating to branch default
40 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 $ cd repo2
42 $ hg pull -r3 ../repo
43 pulling from ../repo
44 searching for changes
45 adding changesets
46 adding manifests
47 adding file changes
48 added 1 changesets with 1 changes to 1 files (+1 heads)
49 (run 'hg heads' to see heads, 'hg merge' to merge)
50
51 Test without --exact and diff.p1 == workingdir.p1
52
53 $ hg up 1
54 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
55 $ hg import ../merge.diff
56 applying ../merge.diff
57 $ tipparents
58 1:540395c44225 changea
59 3:102a90ea7b4a addb
60 $ hg strip --no-backup tip
61 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
62
63 Test without --exact and diff.p1 != workingdir.p1
64
65 $ hg up 2
66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 $ hg import ../merge.diff
68 applying ../merge.diff
69 $ tipparents
70 2:890ecaa90481 addc
71 $ hg strip --no-backup tip
72 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
73
74 Test with --exact
75
76 $ hg import --exact ../merge.diff
77 applying ../merge.diff
78 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
79 $ tipparents
80 1:540395c44225 changea
81 3:102a90ea7b4a addb
82 $ hg strip --no-backup tip
83 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
84
85 Test with --bypass and diff.p1 == workingdir.p1
86
87 $ hg up 1
88 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 $ hg import --bypass ../merge.diff
90 applying ../merge.diff
91 $ tipparents
92 1:540395c44225 changea
93 3:102a90ea7b4a addb
94 $ hg strip --no-backup tip
95
96 Test with --bypass and diff.p1 != workingdir.p1
97
98 $ hg up 2
99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 $ hg import --bypass ../merge.diff
101 applying ../merge.diff
102 $ tipparents
103 2:890ecaa90481 addc
104 $ hg strip --no-backup tip
105
106 Test with --bypass and --exact
107
108 $ hg import --bypass --exact ../merge.diff
109 applying ../merge.diff
110 $ tipparents
111 1:540395c44225 changea
112 3:102a90ea7b4a addb
113 $ hg strip --no-backup tip
114
@@ -1,5666 +1,5695 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, difflib, time, tempfile, errno
12 12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, hbisect
15 15 import sshserver, hgweb, hgweb.server, commandserver
16 16 import match as matchmod
17 17 import merge as mergemod
18 18 import minirst, revset, fileset
19 19 import dagparser, context, simplemerge
20 20 import random, setdiscovery, treediscovery, dagutil
21 21
22 22 table = {}
23 23
24 24 command = cmdutil.command(table)
25 25
26 26 # common command options
27 27
28 28 globalopts = [
29 29 ('R', 'repository', '',
30 30 _('repository root directory or name of overlay bundle file'),
31 31 _('REPO')),
32 32 ('', 'cwd', '',
33 33 _('change working directory'), _('DIR')),
34 34 ('y', 'noninteractive', None,
35 35 _('do not prompt, automatically pick the first choice for all prompts')),
36 36 ('q', 'quiet', None, _('suppress output')),
37 37 ('v', 'verbose', None, _('enable additional output')),
38 38 ('', 'config', [],
39 39 _('set/override config option (use \'section.name=value\')'),
40 40 _('CONFIG')),
41 41 ('', 'debug', None, _('enable debugging output')),
42 42 ('', 'debugger', None, _('start debugger')),
43 43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 44 _('ENCODE')),
45 45 ('', 'encodingmode', encoding.encodingmode,
46 46 _('set the charset encoding mode'), _('MODE')),
47 47 ('', 'traceback', None, _('always print a traceback on exception')),
48 48 ('', 'time', None, _('time how long the command takes')),
49 49 ('', 'profile', None, _('print command execution profile')),
50 50 ('', 'version', None, _('output version information and exit')),
51 51 ('h', 'help', None, _('display help and exit')),
52 52 ]
53 53
54 54 dryrunopts = [('n', 'dry-run', None,
55 55 _('do not perform actions, just print output'))]
56 56
57 57 remoteopts = [
58 58 ('e', 'ssh', '',
59 59 _('specify ssh command to use'), _('CMD')),
60 60 ('', 'remotecmd', '',
61 61 _('specify hg command to run on the remote side'), _('CMD')),
62 62 ('', 'insecure', None,
63 63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 64 ]
65 65
66 66 walkopts = [
67 67 ('I', 'include', [],
68 68 _('include names matching the given patterns'), _('PATTERN')),
69 69 ('X', 'exclude', [],
70 70 _('exclude names matching the given patterns'), _('PATTERN')),
71 71 ]
72 72
73 73 commitopts = [
74 74 ('m', 'message', '',
75 75 _('use text as commit message'), _('TEXT')),
76 76 ('l', 'logfile', '',
77 77 _('read commit message from file'), _('FILE')),
78 78 ]
79 79
80 80 commitopts2 = [
81 81 ('d', 'date', '',
82 82 _('record the specified date as commit date'), _('DATE')),
83 83 ('u', 'user', '',
84 84 _('record the specified user as committer'), _('USER')),
85 85 ]
86 86
87 87 templateopts = [
88 88 ('', 'style', '',
89 89 _('display using template map file'), _('STYLE')),
90 90 ('', 'template', '',
91 91 _('display with template'), _('TEMPLATE')),
92 92 ]
93 93
94 94 logopts = [
95 95 ('p', 'patch', None, _('show patch')),
96 96 ('g', 'git', None, _('use git extended diff format')),
97 97 ('l', 'limit', '',
98 98 _('limit number of changes displayed'), _('NUM')),
99 99 ('M', 'no-merges', None, _('do not show merges')),
100 100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 101 ] + templateopts
102 102
103 103 diffopts = [
104 104 ('a', 'text', None, _('treat all files as text')),
105 105 ('g', 'git', None, _('use git extended diff format')),
106 106 ('', 'nodates', None, _('omit dates from diff headers'))
107 107 ]
108 108
109 109 diffopts2 = [
110 110 ('p', 'show-function', None, _('show which function each change is in')),
111 111 ('', 'reverse', None, _('produce a diff that undoes the changes')),
112 112 ('w', 'ignore-all-space', None,
113 113 _('ignore white space when comparing lines')),
114 114 ('b', 'ignore-space-change', None,
115 115 _('ignore changes in the amount of white space')),
116 116 ('B', 'ignore-blank-lines', None,
117 117 _('ignore changes whose lines are all blank')),
118 118 ('U', 'unified', '',
119 119 _('number of lines of context to show'), _('NUM')),
120 120 ('', 'stat', None, _('output diffstat-style summary of changes')),
121 121 ]
122 122
123 123 mergetoolopts = [
124 124 ('t', 'tool', '', _('specify merge tool')),
125 125 ]
126 126
127 127 similarityopts = [
128 128 ('s', 'similarity', '',
129 129 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
130 130 ]
131 131
132 132 subrepoopts = [
133 133 ('S', 'subrepos', None,
134 134 _('recurse into subrepositories'))
135 135 ]
136 136
137 137 # Commands start here, listed alphabetically
138 138
139 139 @command('^add',
140 140 walkopts + subrepoopts + dryrunopts,
141 141 _('[OPTION]... [FILE]...'))
142 142 def add(ui, repo, *pats, **opts):
143 143 """add the specified files on the next commit
144 144
145 145 Schedule files to be version controlled and added to the
146 146 repository.
147 147
148 148 The files will be added to the repository at the next commit. To
149 149 undo an add before that, see :hg:`forget`.
150 150
151 151 If no names are given, add all files to the repository.
152 152
153 153 .. container:: verbose
154 154
155 155 An example showing how new (unknown) files are added
156 156 automatically by :hg:`add`::
157 157
158 158 $ ls
159 159 foo.c
160 160 $ hg status
161 161 ? foo.c
162 162 $ hg add
163 163 adding foo.c
164 164 $ hg status
165 165 A foo.c
166 166
167 167 Returns 0 if all files are successfully added.
168 168 """
169 169
170 170 m = scmutil.match(repo[None], pats, opts)
171 171 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
172 172 opts.get('subrepos'), prefix="")
173 173 return rejected and 1 or 0
174 174
175 175 @command('addremove',
176 176 similarityopts + walkopts + dryrunopts,
177 177 _('[OPTION]... [FILE]...'))
178 178 def addremove(ui, repo, *pats, **opts):
179 179 """add all new files, delete all missing files
180 180
181 181 Add all new files and remove all missing files from the
182 182 repository.
183 183
184 184 New files are ignored if they match any of the patterns in
185 185 ``.hgignore``. As with add, these changes take effect at the next
186 186 commit.
187 187
188 188 Use the -s/--similarity option to detect renamed files. With a
189 189 parameter greater than 0, this compares every removed file with
190 190 every added file and records those similar enough as renames. This
191 191 option takes a percentage between 0 (disabled) and 100 (files must
192 192 be identical) as its parameter. Detecting renamed files this way
193 193 can be expensive. After using this option, :hg:`status -C` can be
194 194 used to check which files were identified as moved or renamed.
195 195
196 196 Returns 0 if all files are successfully added.
197 197 """
198 198 try:
199 199 sim = float(opts.get('similarity') or 100)
200 200 except ValueError:
201 201 raise util.Abort(_('similarity must be a number'))
202 202 if sim < 0 or sim > 100:
203 203 raise util.Abort(_('similarity must be between 0 and 100'))
204 204 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
205 205
206 206 @command('^annotate|blame',
207 207 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
208 208 ('', 'follow', None,
209 209 _('follow copies/renames and list the filename (DEPRECATED)')),
210 210 ('', 'no-follow', None, _("don't follow copies and renames")),
211 211 ('a', 'text', None, _('treat all files as text')),
212 212 ('u', 'user', None, _('list the author (long with -v)')),
213 213 ('f', 'file', None, _('list the filename')),
214 214 ('d', 'date', None, _('list the date (short with -q)')),
215 215 ('n', 'number', None, _('list the revision number (default)')),
216 216 ('c', 'changeset', None, _('list the changeset')),
217 217 ('l', 'line-number', None, _('show line number at the first appearance'))
218 218 ] + walkopts,
219 219 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
220 220 def annotate(ui, repo, *pats, **opts):
221 221 """show changeset information by line for each file
222 222
223 223 List changes in files, showing the revision id responsible for
224 224 each line
225 225
226 226 This command is useful for discovering when a change was made and
227 227 by whom.
228 228
229 229 Without the -a/--text option, annotate will avoid processing files
230 230 it detects as binary. With -a, annotate will annotate the file
231 231 anyway, although the results will probably be neither useful
232 232 nor desirable.
233 233
234 234 Returns 0 on success.
235 235 """
236 236 if opts.get('follow'):
237 237 # --follow is deprecated and now just an alias for -f/--file
238 238 # to mimic the behavior of Mercurial before version 1.5
239 239 opts['file'] = True
240 240
241 241 datefunc = ui.quiet and util.shortdate or util.datestr
242 242 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
243 243
244 244 if not pats:
245 245 raise util.Abort(_('at least one filename or pattern is required'))
246 246
247 247 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
248 248 ('number', ' ', lambda x: str(x[0].rev())),
249 249 ('changeset', ' ', lambda x: short(x[0].node())),
250 250 ('date', ' ', getdate),
251 251 ('file', ' ', lambda x: x[0].path()),
252 252 ('line_number', ':', lambda x: str(x[1])),
253 253 ]
254 254
255 255 if (not opts.get('user') and not opts.get('changeset')
256 256 and not opts.get('date') and not opts.get('file')):
257 257 opts['number'] = True
258 258
259 259 linenumber = opts.get('line_number') is not None
260 260 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
261 261 raise util.Abort(_('at least one of -n/-c is required for -l'))
262 262
263 263 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
264 264 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
265 265
266 266 def bad(x, y):
267 267 raise util.Abort("%s: %s" % (x, y))
268 268
269 269 ctx = scmutil.revsingle(repo, opts.get('rev'))
270 270 m = scmutil.match(ctx, pats, opts)
271 271 m.bad = bad
272 272 follow = not opts.get('no_follow')
273 273 for abs in ctx.walk(m):
274 274 fctx = ctx[abs]
275 275 if not opts.get('text') and util.binary(fctx.data()):
276 276 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
277 277 continue
278 278
279 279 lines = fctx.annotate(follow=follow, linenumber=linenumber)
280 280 pieces = []
281 281
282 282 for f, sep in funcmap:
283 283 l = [f(n) for n, dummy in lines]
284 284 if l:
285 285 sized = [(x, encoding.colwidth(x)) for x in l]
286 286 ml = max([w for x, w in sized])
287 287 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
288 288 for x, w in sized])
289 289
290 290 if pieces:
291 291 for p, l in zip(zip(*pieces), lines):
292 292 ui.write("%s: %s" % ("".join(p), l[1]))
293 293
294 294 @command('archive',
295 295 [('', 'no-decode', None, _('do not pass files through decoders')),
296 296 ('p', 'prefix', '', _('directory prefix for files in archive'),
297 297 _('PREFIX')),
298 298 ('r', 'rev', '', _('revision to distribute'), _('REV')),
299 299 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
300 300 ] + subrepoopts + walkopts,
301 301 _('[OPTION]... DEST'))
302 302 def archive(ui, repo, dest, **opts):
303 303 '''create an unversioned archive of a repository revision
304 304
305 305 By default, the revision used is the parent of the working
306 306 directory; use -r/--rev to specify a different revision.
307 307
308 308 The archive type is automatically detected based on file
309 309 extension (or override using -t/--type).
310 310
311 311 .. container:: verbose
312 312
313 313 Examples:
314 314
315 315 - create a zip file containing the 1.0 release::
316 316
317 317 hg archive -r 1.0 project-1.0.zip
318 318
319 319 - create a tarball excluding .hg files::
320 320
321 321 hg archive project.tar.gz -X ".hg*"
322 322
323 323 Valid types are:
324 324
325 325 :``files``: a directory full of files (default)
326 326 :``tar``: tar archive, uncompressed
327 327 :``tbz2``: tar archive, compressed using bzip2
328 328 :``tgz``: tar archive, compressed using gzip
329 329 :``uzip``: zip archive, uncompressed
330 330 :``zip``: zip archive, compressed using deflate
331 331
332 332 The exact name of the destination archive or directory is given
333 333 using a format string; see :hg:`help export` for details.
334 334
335 335 Each member added to an archive file has a directory prefix
336 336 prepended. Use -p/--prefix to specify a format string for the
337 337 prefix. The default is the basename of the archive, with suffixes
338 338 removed.
339 339
340 340 Returns 0 on success.
341 341 '''
342 342
343 343 ctx = scmutil.revsingle(repo, opts.get('rev'))
344 344 if not ctx:
345 345 raise util.Abort(_('no working directory: please specify a revision'))
346 346 node = ctx.node()
347 347 dest = cmdutil.makefilename(repo, dest, node)
348 348 if os.path.realpath(dest) == repo.root:
349 349 raise util.Abort(_('repository root cannot be destination'))
350 350
351 351 kind = opts.get('type') or archival.guesskind(dest) or 'files'
352 352 prefix = opts.get('prefix')
353 353
354 354 if dest == '-':
355 355 if kind == 'files':
356 356 raise util.Abort(_('cannot archive plain files to stdout'))
357 357 dest = cmdutil.makefileobj(repo, dest)
358 358 if not prefix:
359 359 prefix = os.path.basename(repo.root) + '-%h'
360 360
361 361 prefix = cmdutil.makefilename(repo, prefix, node)
362 362 matchfn = scmutil.match(ctx, [], opts)
363 363 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
364 364 matchfn, prefix, subrepos=opts.get('subrepos'))
365 365
366 366 @command('backout',
367 367 [('', 'merge', None, _('merge with old dirstate parent after backout')),
368 368 ('', 'parent', '',
369 369 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
370 370 ('r', 'rev', '', _('revision to backout'), _('REV')),
371 371 ] + mergetoolopts + walkopts + commitopts + commitopts2,
372 372 _('[OPTION]... [-r] REV'))
373 373 def backout(ui, repo, node=None, rev=None, **opts):
374 374 '''reverse effect of earlier changeset
375 375
376 376 Prepare a new changeset with the effect of REV undone in the
377 377 current working directory.
378 378
379 379 If REV is the parent of the working directory, then this new changeset
380 380 is committed automatically. Otherwise, hg needs to merge the
381 381 changes and the merged result is left uncommitted.
382 382
383 383 .. note::
384 384 backout cannot be used to fix either an unwanted or
385 385 incorrect merge.
386 386
387 387 .. container:: verbose
388 388
389 389 By default, the pending changeset will have one parent,
390 390 maintaining a linear history. With --merge, the pending
391 391 changeset will instead have two parents: the old parent of the
392 392 working directory and a new child of REV that simply undoes REV.
393 393
394 394 Before version 1.7, the behavior without --merge was equivalent
395 395 to specifying --merge followed by :hg:`update --clean .` to
396 396 cancel the merge and leave the child of REV as a head to be
397 397 merged separately.
398 398
399 399 See :hg:`help dates` for a list of formats valid for -d/--date.
400 400
401 401 Returns 0 on success.
402 402 '''
403 403 if rev and node:
404 404 raise util.Abort(_("please specify just one revision"))
405 405
406 406 if not rev:
407 407 rev = node
408 408
409 409 if not rev:
410 410 raise util.Abort(_("please specify a revision to backout"))
411 411
412 412 date = opts.get('date')
413 413 if date:
414 414 opts['date'] = util.parsedate(date)
415 415
416 416 cmdutil.bailifchanged(repo)
417 417 node = scmutil.revsingle(repo, rev).node()
418 418
419 419 op1, op2 = repo.dirstate.parents()
420 420 a = repo.changelog.ancestor(op1, node)
421 421 if a != node:
422 422 raise util.Abort(_('cannot backout change on a different branch'))
423 423
424 424 p1, p2 = repo.changelog.parents(node)
425 425 if p1 == nullid:
426 426 raise util.Abort(_('cannot backout a change with no parents'))
427 427 if p2 != nullid:
428 428 if not opts.get('parent'):
429 429 raise util.Abort(_('cannot backout a merge changeset'))
430 430 p = repo.lookup(opts['parent'])
431 431 if p not in (p1, p2):
432 432 raise util.Abort(_('%s is not a parent of %s') %
433 433 (short(p), short(node)))
434 434 parent = p
435 435 else:
436 436 if opts.get('parent'):
437 437 raise util.Abort(_('cannot use --parent on non-merge changeset'))
438 438 parent = p1
439 439
440 440 # the backout should appear on the same branch
441 441 branch = repo.dirstate.branch()
442 442 hg.clean(repo, node, show_stats=False)
443 443 repo.dirstate.setbranch(branch)
444 444 revert_opts = opts.copy()
445 445 revert_opts['date'] = None
446 446 revert_opts['all'] = True
447 447 revert_opts['rev'] = hex(parent)
448 448 revert_opts['no_backup'] = None
449 449 revert(ui, repo, **revert_opts)
450 450 if not opts.get('merge') and op1 != node:
451 451 try:
452 452 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
453 453 return hg.update(repo, op1)
454 454 finally:
455 455 ui.setconfig('ui', 'forcemerge', '')
456 456
457 457 commit_opts = opts.copy()
458 458 commit_opts['addremove'] = False
459 459 if not commit_opts['message'] and not commit_opts['logfile']:
460 460 # we don't translate commit messages
461 461 commit_opts['message'] = "Backed out changeset %s" % short(node)
462 462 commit_opts['force_editor'] = True
463 463 commit(ui, repo, **commit_opts)
464 464 def nice(node):
465 465 return '%d:%s' % (repo.changelog.rev(node), short(node))
466 466 ui.status(_('changeset %s backs out changeset %s\n') %
467 467 (nice(repo.changelog.tip()), nice(node)))
468 468 if opts.get('merge') and op1 != node:
469 469 hg.clean(repo, op1, show_stats=False)
470 470 ui.status(_('merging with changeset %s\n')
471 471 % nice(repo.changelog.tip()))
472 472 try:
473 473 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
474 474 return hg.merge(repo, hex(repo.changelog.tip()))
475 475 finally:
476 476 ui.setconfig('ui', 'forcemerge', '')
477 477 return 0
478 478
479 479 @command('bisect',
480 480 [('r', 'reset', False, _('reset bisect state')),
481 481 ('g', 'good', False, _('mark changeset good')),
482 482 ('b', 'bad', False, _('mark changeset bad')),
483 483 ('s', 'skip', False, _('skip testing changeset')),
484 484 ('e', 'extend', False, _('extend the bisect range')),
485 485 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
486 486 ('U', 'noupdate', False, _('do not update to target'))],
487 487 _("[-gbsr] [-U] [-c CMD] [REV]"))
488 488 def bisect(ui, repo, rev=None, extra=None, command=None,
489 489 reset=None, good=None, bad=None, skip=None, extend=None,
490 490 noupdate=None):
491 491 """subdivision search of changesets
492 492
493 493 This command helps to find changesets which introduce problems. To
494 494 use, mark the earliest changeset you know exhibits the problem as
495 495 bad, then mark the latest changeset which is free from the problem
496 496 as good. Bisect will update your working directory to a revision
497 497 for testing (unless the -U/--noupdate option is specified). Once
498 498 you have performed tests, mark the working directory as good or
499 499 bad, and bisect will either update to another candidate changeset
500 500 or announce that it has found the bad revision.
501 501
502 502 As a shortcut, you can also use the revision argument to mark a
503 503 revision as good or bad without checking it out first.
504 504
505 505 If you supply a command, it will be used for automatic bisection.
506 506 Its exit status will be used to mark revisions as good or bad:
507 507 status 0 means good, 125 means to skip the revision, 127
508 508 (command not found) will abort the bisection, and any other
509 509 non-zero exit status means the revision is bad.
510 510
511 511 .. container:: verbose
512 512
513 513 Some examples:
514 514
515 515 - start a bisection with known bad revision 12, and good revision 34::
516 516
517 517 hg bisect --bad 34
518 518 hg bisect --good 12
519 519
520 520 - advance the current bisection by marking current revision as good or
521 521 bad::
522 522
523 523 hg bisect --good
524 524 hg bisect --bad
525 525
526 526 - mark the current revision, or a known revision, to be skipped (eg. if
527 527 that revision is not usable because of another issue)::
528 528
529 529 hg bisect --skip
530 530 hg bisect --skip 23
531 531
532 532 - forget the current bisection::
533 533
534 534 hg bisect --reset
535 535
536 536 - use 'make && make tests' to automatically find the first broken
537 537 revision::
538 538
539 539 hg bisect --reset
540 540 hg bisect --bad 34
541 541 hg bisect --good 12
542 542 hg bisect --command 'make && make tests'
543 543
544 544 - see all changesets whose states are already known in the current
545 545 bisection::
546 546
547 547 hg log -r "bisect(pruned)"
548 548
549 549 - see all changesets that took part in the current bisection::
550 550
551 551 hg log -r "bisect(range)"
552 552
553 553 - with the graphlog extension, you can even get a nice graph::
554 554
555 555 hg log --graph -r "bisect(range)"
556 556
557 557 See :hg:`help revsets` for more about the `bisect()` keyword.
558 558
559 559 Returns 0 on success.
560 560 """
561 561 def extendbisectrange(nodes, good):
562 562 # bisect is incomplete when it ends on a merge node and
563 563 # one of the parent was not checked.
564 564 parents = repo[nodes[0]].parents()
565 565 if len(parents) > 1:
566 566 side = good and state['bad'] or state['good']
567 567 num = len(set(i.node() for i in parents) & set(side))
568 568 if num == 1:
569 569 return parents[0].ancestor(parents[1])
570 570 return None
571 571
572 572 def print_result(nodes, good):
573 573 displayer = cmdutil.show_changeset(ui, repo, {})
574 574 if len(nodes) == 1:
575 575 # narrowed it down to a single revision
576 576 if good:
577 577 ui.write(_("The first good revision is:\n"))
578 578 else:
579 579 ui.write(_("The first bad revision is:\n"))
580 580 displayer.show(repo[nodes[0]])
581 581 extendnode = extendbisectrange(nodes, good)
582 582 if extendnode is not None:
583 583 ui.write(_('Not all ancestors of this changeset have been'
584 584 ' checked.\nUse bisect --extend to continue the '
585 585 'bisection from\nthe common ancestor, %s.\n')
586 586 % extendnode)
587 587 else:
588 588 # multiple possible revisions
589 589 if good:
590 590 ui.write(_("Due to skipped revisions, the first "
591 591 "good revision could be any of:\n"))
592 592 else:
593 593 ui.write(_("Due to skipped revisions, the first "
594 594 "bad revision could be any of:\n"))
595 595 for n in nodes:
596 596 displayer.show(repo[n])
597 597 displayer.close()
598 598
599 599 def check_state(state, interactive=True):
600 600 if not state['good'] or not state['bad']:
601 601 if (good or bad or skip or reset) and interactive:
602 602 return
603 603 if not state['good']:
604 604 raise util.Abort(_('cannot bisect (no known good revisions)'))
605 605 else:
606 606 raise util.Abort(_('cannot bisect (no known bad revisions)'))
607 607 return True
608 608
609 609 # backward compatibility
610 610 if rev in "good bad reset init".split():
611 611 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
612 612 cmd, rev, extra = rev, extra, None
613 613 if cmd == "good":
614 614 good = True
615 615 elif cmd == "bad":
616 616 bad = True
617 617 else:
618 618 reset = True
619 619 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
620 620 raise util.Abort(_('incompatible arguments'))
621 621
622 622 if reset:
623 623 p = repo.join("bisect.state")
624 624 if os.path.exists(p):
625 625 os.unlink(p)
626 626 return
627 627
628 628 state = hbisect.load_state(repo)
629 629
630 630 if command:
631 631 changesets = 1
632 632 try:
633 633 while changesets:
634 634 # update state
635 635 status = util.system(command, out=ui.fout)
636 636 if status == 125:
637 637 transition = "skip"
638 638 elif status == 0:
639 639 transition = "good"
640 640 # status < 0 means process was killed
641 641 elif status == 127:
642 642 raise util.Abort(_("failed to execute %s") % command)
643 643 elif status < 0:
644 644 raise util.Abort(_("%s killed") % command)
645 645 else:
646 646 transition = "bad"
647 647 ctx = scmutil.revsingle(repo, rev)
648 648 rev = None # clear for future iterations
649 649 state[transition].append(ctx.node())
650 650 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
651 651 check_state(state, interactive=False)
652 652 # bisect
653 653 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
654 654 # update to next check
655 655 cmdutil.bailifchanged(repo)
656 656 hg.clean(repo, nodes[0], show_stats=False)
657 657 finally:
658 658 hbisect.save_state(repo, state)
659 659 print_result(nodes, good)
660 660 return
661 661
662 662 # update state
663 663
664 664 if rev:
665 665 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
666 666 else:
667 667 nodes = [repo.lookup('.')]
668 668
669 669 if good or bad or skip:
670 670 if good:
671 671 state['good'] += nodes
672 672 elif bad:
673 673 state['bad'] += nodes
674 674 elif skip:
675 675 state['skip'] += nodes
676 676 hbisect.save_state(repo, state)
677 677
678 678 if not check_state(state):
679 679 return
680 680
681 681 # actually bisect
682 682 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
683 683 if extend:
684 684 if not changesets:
685 685 extendnode = extendbisectrange(nodes, good)
686 686 if extendnode is not None:
687 687 ui.write(_("Extending search to changeset %d:%s\n"
688 688 % (extendnode.rev(), extendnode)))
689 689 if noupdate:
690 690 return
691 691 cmdutil.bailifchanged(repo)
692 692 return hg.clean(repo, extendnode.node())
693 693 raise util.Abort(_("nothing to extend"))
694 694
695 695 if changesets == 0:
696 696 print_result(nodes, good)
697 697 else:
698 698 assert len(nodes) == 1 # only a single node can be tested next
699 699 node = nodes[0]
700 700 # compute the approximate number of remaining tests
701 701 tests, size = 0, 2
702 702 while size <= changesets:
703 703 tests, size = tests + 1, size * 2
704 704 rev = repo.changelog.rev(node)
705 705 ui.write(_("Testing changeset %d:%s "
706 706 "(%d changesets remaining, ~%d tests)\n")
707 707 % (rev, short(node), changesets, tests))
708 708 if not noupdate:
709 709 cmdutil.bailifchanged(repo)
710 710 return hg.clean(repo, node)
711 711
712 712 @command('bookmarks',
713 713 [('f', 'force', False, _('force')),
714 714 ('r', 'rev', '', _('revision'), _('REV')),
715 715 ('d', 'delete', False, _('delete a given bookmark')),
716 716 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
717 717 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
718 718 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
719 719 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
720 720 rename=None, inactive=False):
721 721 '''track a line of development with movable markers
722 722
723 723 Bookmarks are pointers to certain commits that move when
724 724 committing. Bookmarks are local. They can be renamed, copied and
725 725 deleted. It is possible to use bookmark names in :hg:`merge` and
726 726 :hg:`update` to merge and update respectively to a given bookmark.
727 727
728 728 You can use :hg:`bookmark NAME` to set a bookmark on the working
729 729 directory's parent revision with the given name. If you specify
730 730 a revision using -r REV (where REV may be an existing bookmark),
731 731 the bookmark is assigned to that revision.
732 732
733 733 Bookmarks can be pushed and pulled between repositories (see :hg:`help
734 734 push` and :hg:`help pull`). This requires both the local and remote
735 735 repositories to support bookmarks. For versions prior to 1.8, this means
736 736 the bookmarks extension must be enabled.
737 737 '''
738 738 hexfn = ui.debugflag and hex or short
739 739 marks = repo._bookmarks
740 740 cur = repo.changectx('.').node()
741 741
742 742 if delete:
743 743 if mark is None:
744 744 raise util.Abort(_("bookmark name required"))
745 745 if mark not in marks:
746 746 raise util.Abort(_("bookmark '%s' does not exist") % mark)
747 747 if mark == repo._bookmarkcurrent:
748 748 bookmarks.setcurrent(repo, None)
749 749 del marks[mark]
750 750 bookmarks.write(repo)
751 751 return
752 752
753 753 if rename:
754 754 if rename not in marks:
755 755 raise util.Abort(_("bookmark '%s' does not exist") % rename)
756 756 if mark in marks and not force:
757 757 raise util.Abort(_("bookmark '%s' already exists "
758 758 "(use -f to force)") % mark)
759 759 if mark is None:
760 760 raise util.Abort(_("new bookmark name required"))
761 761 marks[mark] = marks[rename]
762 762 if repo._bookmarkcurrent == rename and not inactive:
763 763 bookmarks.setcurrent(repo, mark)
764 764 del marks[rename]
765 765 bookmarks.write(repo)
766 766 return
767 767
768 768 if mark is not None:
769 769 if "\n" in mark:
770 770 raise util.Abort(_("bookmark name cannot contain newlines"))
771 771 mark = mark.strip()
772 772 if not mark:
773 773 raise util.Abort(_("bookmark names cannot consist entirely of "
774 774 "whitespace"))
775 775 if inactive and mark == repo._bookmarkcurrent:
776 776 bookmarks.setcurrent(repo, None)
777 777 return
778 778 if mark in marks and not force:
779 779 raise util.Abort(_("bookmark '%s' already exists "
780 780 "(use -f to force)") % mark)
781 781 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
782 782 and not force):
783 783 raise util.Abort(
784 784 _("a bookmark cannot have the name of an existing branch"))
785 785 if rev:
786 786 marks[mark] = repo.lookup(rev)
787 787 else:
788 788 marks[mark] = cur
789 789 if not inactive and cur == marks[mark]:
790 790 bookmarks.setcurrent(repo, mark)
791 791 bookmarks.write(repo)
792 792 return
793 793
794 794 if mark is None:
795 795 if rev:
796 796 raise util.Abort(_("bookmark name required"))
797 797 if len(marks) == 0:
798 798 ui.status(_("no bookmarks set\n"))
799 799 else:
800 800 for bmark, n in sorted(marks.iteritems()):
801 801 current = repo._bookmarkcurrent
802 802 if bmark == current and n == cur:
803 803 prefix, label = '*', 'bookmarks.current'
804 804 else:
805 805 prefix, label = ' ', ''
806 806
807 807 if ui.quiet:
808 808 ui.write("%s\n" % bmark, label=label)
809 809 else:
810 810 ui.write(" %s %-25s %d:%s\n" % (
811 811 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
812 812 label=label)
813 813 return
814 814
815 815 @command('branch',
816 816 [('f', 'force', None,
817 817 _('set branch name even if it shadows an existing branch')),
818 818 ('C', 'clean', None, _('reset branch name to parent branch name'))],
819 819 _('[-fC] [NAME]'))
820 820 def branch(ui, repo, label=None, **opts):
821 821 """set or show the current branch name
822 822
823 823 With no argument, show the current branch name. With one argument,
824 824 set the working directory branch name (the branch will not exist
825 825 in the repository until the next commit). Standard practice
826 826 recommends that primary development take place on the 'default'
827 827 branch.
828 828
829 829 Unless -f/--force is specified, branch will not let you set a
830 830 branch name that already exists, even if it's inactive.
831 831
832 832 Use -C/--clean to reset the working directory branch to that of
833 833 the parent of the working directory, negating a previous branch
834 834 change.
835 835
836 836 Use the command :hg:`update` to switch to an existing branch. Use
837 837 :hg:`commit --close-branch` to mark this branch as closed.
838 838
839 839 .. note::
840 840 Branch names are permanent. Use :hg:`bookmark` to create a
841 841 light-weight bookmark instead. See :hg:`help glossary` for more
842 842 information about named branches and bookmarks.
843 843
844 844 Returns 0 on success.
845 845 """
846 846
847 847 if opts.get('clean'):
848 848 label = repo[None].p1().branch()
849 849 repo.dirstate.setbranch(label)
850 850 ui.status(_('reset working directory to branch %s\n') % label)
851 851 elif label:
852 852 if not opts.get('force') and label in repo.branchtags():
853 853 if label not in [p.branch() for p in repo.parents()]:
854 854 raise util.Abort(_('a branch of the same name already exists'),
855 855 # i18n: "it" refers to an existing branch
856 856 hint=_("use 'hg update' to switch to it"))
857 857 repo.dirstate.setbranch(label)
858 858 ui.status(_('marked working directory as branch %s\n') % label)
859 859 else:
860 860 ui.write("%s\n" % repo.dirstate.branch())
861 861
862 862 @command('branches',
863 863 [('a', 'active', False, _('show only branches that have unmerged heads')),
864 864 ('c', 'closed', False, _('show normal and closed branches'))],
865 865 _('[-ac]'))
866 866 def branches(ui, repo, active=False, closed=False):
867 867 """list repository named branches
868 868
869 869 List the repository's named branches, indicating which ones are
870 870 inactive. If -c/--closed is specified, also list branches which have
871 871 been marked closed (see :hg:`commit --close-branch`).
872 872
873 873 If -a/--active is specified, only show active branches. A branch
874 874 is considered active if it contains repository heads.
875 875
876 876 Use the command :hg:`update` to switch to an existing branch.
877 877
878 878 Returns 0.
879 879 """
880 880
881 881 hexfunc = ui.debugflag and hex or short
882 882 activebranches = [repo[n].branch() for n in repo.heads()]
883 883 def testactive(tag, node):
884 884 realhead = tag in activebranches
885 885 open = node in repo.branchheads(tag, closed=False)
886 886 return realhead and open
887 887 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
888 888 for tag, node in repo.branchtags().items()],
889 889 reverse=True)
890 890
891 891 for isactive, node, tag in branches:
892 892 if (not active) or isactive:
893 893 if ui.quiet:
894 894 ui.write("%s\n" % tag)
895 895 else:
896 896 hn = repo.lookup(node)
897 897 if isactive:
898 898 label = 'branches.active'
899 899 notice = ''
900 900 elif hn not in repo.branchheads(tag, closed=False):
901 901 if not closed:
902 902 continue
903 903 label = 'branches.closed'
904 904 notice = _(' (closed)')
905 905 else:
906 906 label = 'branches.inactive'
907 907 notice = _(' (inactive)')
908 908 if tag == repo.dirstate.branch():
909 909 label = 'branches.current'
910 910 rev = str(node).rjust(31 - encoding.colwidth(tag))
911 911 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
912 912 tag = ui.label(tag, label)
913 913 ui.write("%s %s%s\n" % (tag, rev, notice))
914 914
915 915 @command('bundle',
916 916 [('f', 'force', None, _('run even when the destination is unrelated')),
917 917 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
918 918 _('REV')),
919 919 ('b', 'branch', [], _('a specific branch you would like to bundle'),
920 920 _('BRANCH')),
921 921 ('', 'base', [],
922 922 _('a base changeset assumed to be available at the destination'),
923 923 _('REV')),
924 924 ('a', 'all', None, _('bundle all changesets in the repository')),
925 925 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
926 926 ] + remoteopts,
927 927 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
928 928 def bundle(ui, repo, fname, dest=None, **opts):
929 929 """create a changegroup file
930 930
931 931 Generate a compressed changegroup file collecting changesets not
932 932 known to be in another repository.
933 933
934 934 If you omit the destination repository, then hg assumes the
935 935 destination will have all the nodes you specify with --base
936 936 parameters. To create a bundle containing all changesets, use
937 937 -a/--all (or --base null).
938 938
939 939 You can change compression method with the -t/--type option.
940 940 The available compression methods are: none, bzip2, and
941 941 gzip (by default, bundles are compressed using bzip2).
942 942
943 943 The bundle file can then be transferred using conventional means
944 944 and applied to another repository with the unbundle or pull
945 945 command. This is useful when direct push and pull are not
946 946 available or when exporting an entire repository is undesirable.
947 947
948 948 Applying bundles preserves all changeset contents including
949 949 permissions, copy/rename information, and revision history.
950 950
951 951 Returns 0 on success, 1 if no changes found.
952 952 """
953 953 revs = None
954 954 if 'rev' in opts:
955 955 revs = scmutil.revrange(repo, opts['rev'])
956 956
957 957 if opts.get('all'):
958 958 base = ['null']
959 959 else:
960 960 base = scmutil.revrange(repo, opts.get('base'))
961 961 if base:
962 962 if dest:
963 963 raise util.Abort(_("--base is incompatible with specifying "
964 964 "a destination"))
965 965 common = [repo.lookup(rev) for rev in base]
966 966 heads = revs and map(repo.lookup, revs) or revs
967 967 else:
968 968 dest = ui.expandpath(dest or 'default-push', dest or 'default')
969 969 dest, branches = hg.parseurl(dest, opts.get('branch'))
970 970 other = hg.peer(repo, opts, dest)
971 971 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
972 972 heads = revs and map(repo.lookup, revs) or revs
973 973 common, outheads = discovery.findcommonoutgoing(repo, other,
974 974 onlyheads=heads,
975 975 force=opts.get('force'))
976 976
977 977 cg = repo.getbundle('bundle', common=common, heads=heads)
978 978 if not cg:
979 979 ui.status(_("no changes found\n"))
980 980 return 1
981 981
982 982 bundletype = opts.get('type', 'bzip2').lower()
983 983 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
984 984 bundletype = btypes.get(bundletype)
985 985 if bundletype not in changegroup.bundletypes:
986 986 raise util.Abort(_('unknown bundle type specified with --type'))
987 987
988 988 changegroup.writebundle(cg, fname, bundletype)
989 989
990 990 @command('cat',
991 991 [('o', 'output', '',
992 992 _('print output to file with formatted name'), _('FORMAT')),
993 993 ('r', 'rev', '', _('print the given revision'), _('REV')),
994 994 ('', 'decode', None, _('apply any matching decode filter')),
995 995 ] + walkopts,
996 996 _('[OPTION]... FILE...'))
997 997 def cat(ui, repo, file1, *pats, **opts):
998 998 """output the current or given revision of files
999 999
1000 1000 Print the specified files as they were at the given revision. If
1001 1001 no revision is given, the parent of the working directory is used,
1002 1002 or tip if no revision is checked out.
1003 1003
1004 1004 Output may be to a file, in which case the name of the file is
1005 1005 given using a format string. The formatting rules are the same as
1006 1006 for the export command, with the following additions:
1007 1007
1008 1008 :``%s``: basename of file being printed
1009 1009 :``%d``: dirname of file being printed, or '.' if in repository root
1010 1010 :``%p``: root-relative path name of file being printed
1011 1011
1012 1012 Returns 0 on success.
1013 1013 """
1014 1014 ctx = scmutil.revsingle(repo, opts.get('rev'))
1015 1015 err = 1
1016 1016 m = scmutil.match(ctx, (file1,) + pats, opts)
1017 1017 for abs in ctx.walk(m):
1018 1018 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1019 1019 pathname=abs)
1020 1020 data = ctx[abs].data()
1021 1021 if opts.get('decode'):
1022 1022 data = repo.wwritedata(abs, data)
1023 1023 fp.write(data)
1024 1024 fp.close()
1025 1025 err = 0
1026 1026 return err
1027 1027
1028 1028 @command('^clone',
1029 1029 [('U', 'noupdate', None,
1030 1030 _('the clone will include an empty working copy (only a repository)')),
1031 1031 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1032 1032 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1033 1033 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1034 1034 ('', 'pull', None, _('use pull protocol to copy metadata')),
1035 1035 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1036 1036 ] + remoteopts,
1037 1037 _('[OPTION]... SOURCE [DEST]'))
1038 1038 def clone(ui, source, dest=None, **opts):
1039 1039 """make a copy of an existing repository
1040 1040
1041 1041 Create a copy of an existing repository in a new directory.
1042 1042
1043 1043 If no destination directory name is specified, it defaults to the
1044 1044 basename of the source.
1045 1045
1046 1046 The location of the source is added to the new repository's
1047 1047 ``.hg/hgrc`` file, as the default to be used for future pulls.
1048 1048
1049 1049 Only local paths and ``ssh://`` URLs are supported as
1050 1050 destinations. For ``ssh://`` destinations, no working directory or
1051 1051 ``.hg/hgrc`` will be created on the remote side.
1052 1052
1053 1053 To pull only a subset of changesets, specify one or more revisions
1054 1054 identifiers with -r/--rev or branches with -b/--branch. The
1055 1055 resulting clone will contain only the specified changesets and
1056 1056 their ancestors. These options (or 'clone src#rev dest') imply
1057 1057 --pull, even for local source repositories. Note that specifying a
1058 1058 tag will include the tagged changeset but not the changeset
1059 1059 containing the tag.
1060 1060
1061 1061 To check out a particular version, use -u/--update, or
1062 1062 -U/--noupdate to create a clone with no working directory.
1063 1063
1064 1064 .. container:: verbose
1065 1065
1066 1066 For efficiency, hardlinks are used for cloning whenever the
1067 1067 source and destination are on the same filesystem (note this
1068 1068 applies only to the repository data, not to the working
1069 1069 directory). Some filesystems, such as AFS, implement hardlinking
1070 1070 incorrectly, but do not report errors. In these cases, use the
1071 1071 --pull option to avoid hardlinking.
1072 1072
1073 1073 In some cases, you can clone repositories and the working
1074 1074 directory using full hardlinks with ::
1075 1075
1076 1076 $ cp -al REPO REPOCLONE
1077 1077
1078 1078 This is the fastest way to clone, but it is not always safe. The
1079 1079 operation is not atomic (making sure REPO is not modified during
1080 1080 the operation is up to you) and you have to make sure your
1081 1081 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1082 1082 so). Also, this is not compatible with certain extensions that
1083 1083 place their metadata under the .hg directory, such as mq.
1084 1084
1085 1085 Mercurial will update the working directory to the first applicable
1086 1086 revision from this list:
1087 1087
1088 1088 a) null if -U or the source repository has no changesets
1089 1089 b) if -u . and the source repository is local, the first parent of
1090 1090 the source repository's working directory
1091 1091 c) the changeset specified with -u (if a branch name, this means the
1092 1092 latest head of that branch)
1093 1093 d) the changeset specified with -r
1094 1094 e) the tipmost head specified with -b
1095 1095 f) the tipmost head specified with the url#branch source syntax
1096 1096 g) the tipmost head of the default branch
1097 1097 h) tip
1098 1098
1099 1099 Examples:
1100 1100
1101 1101 - clone a remote repository to a new directory named hg/::
1102 1102
1103 1103 hg clone http://selenic.com/hg
1104 1104
1105 1105 - create a lightweight local clone::
1106 1106
1107 1107 hg clone project/ project-feature/
1108 1108
1109 1109 - clone from an absolute path on an ssh server (note double-slash)::
1110 1110
1111 1111 hg clone ssh://user@server//home/projects/alpha/
1112 1112
1113 1113 - do a high-speed clone over a LAN while checking out a
1114 1114 specified version::
1115 1115
1116 1116 hg clone --uncompressed http://server/repo -u 1.5
1117 1117
1118 1118 - create a repository without changesets after a particular revision::
1119 1119
1120 1120 hg clone -r 04e544 experimental/ good/
1121 1121
1122 1122 - clone (and track) a particular named branch::
1123 1123
1124 1124 hg clone http://selenic.com/hg#stable
1125 1125
1126 1126 See :hg:`help urls` for details on specifying URLs.
1127 1127
1128 1128 Returns 0 on success.
1129 1129 """
1130 1130 if opts.get('noupdate') and opts.get('updaterev'):
1131 1131 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1132 1132
1133 1133 r = hg.clone(ui, opts, source, dest,
1134 1134 pull=opts.get('pull'),
1135 1135 stream=opts.get('uncompressed'),
1136 1136 rev=opts.get('rev'),
1137 1137 update=opts.get('updaterev') or not opts.get('noupdate'),
1138 1138 branch=opts.get('branch'))
1139 1139
1140 1140 return r is None
1141 1141
1142 1142 @command('^commit|ci',
1143 1143 [('A', 'addremove', None,
1144 1144 _('mark new/missing files as added/removed before committing')),
1145 1145 ('', 'close-branch', None,
1146 1146 _('mark a branch as closed, hiding it from the branch list')),
1147 1147 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1148 1148 _('[OPTION]... [FILE]...'))
1149 1149 def commit(ui, repo, *pats, **opts):
1150 1150 """commit the specified files or all outstanding changes
1151 1151
1152 1152 Commit changes to the given files into the repository. Unlike a
1153 1153 centralized SCM, this operation is a local operation. See
1154 1154 :hg:`push` for a way to actively distribute your changes.
1155 1155
1156 1156 If a list of files is omitted, all changes reported by :hg:`status`
1157 1157 will be committed.
1158 1158
1159 1159 If you are committing the result of a merge, do not provide any
1160 1160 filenames or -I/-X filters.
1161 1161
1162 1162 If no commit message is specified, Mercurial starts your
1163 1163 configured editor where you can enter a message. In case your
1164 1164 commit fails, you will find a backup of your message in
1165 1165 ``.hg/last-message.txt``.
1166 1166
1167 1167 See :hg:`help dates` for a list of formats valid for -d/--date.
1168 1168
1169 1169 Returns 0 on success, 1 if nothing changed.
1170 1170 """
1171 1171 if opts.get('subrepos'):
1172 1172 # Let --subrepos on the command line overide config setting.
1173 1173 ui.setconfig('ui', 'commitsubrepos', True)
1174 1174
1175 1175 extra = {}
1176 1176 if opts.get('close_branch'):
1177 1177 if repo['.'].node() not in repo.branchheads():
1178 1178 # The topo heads set is included in the branch heads set of the
1179 1179 # current branch, so it's sufficient to test branchheads
1180 1180 raise util.Abort(_('can only close branch heads'))
1181 1181 extra['close'] = 1
1182 1182 e = cmdutil.commiteditor
1183 1183 if opts.get('force_editor'):
1184 1184 e = cmdutil.commitforceeditor
1185 1185
1186 1186 def commitfunc(ui, repo, message, match, opts):
1187 1187 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1188 1188 editor=e, extra=extra)
1189 1189
1190 1190 branch = repo[None].branch()
1191 1191 bheads = repo.branchheads(branch)
1192 1192
1193 1193 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1194 1194 if not node:
1195 1195 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1196 1196 if stat[3]:
1197 1197 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1198 1198 % len(stat[3]))
1199 1199 else:
1200 1200 ui.status(_("nothing changed\n"))
1201 1201 return 1
1202 1202
1203 1203 ctx = repo[node]
1204 1204 parents = ctx.parents()
1205 1205
1206 1206 if (bheads and node not in bheads and not
1207 1207 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1208 1208 ui.status(_('created new head\n'))
1209 1209 # The message is not printed for initial roots. For the other
1210 1210 # changesets, it is printed in the following situations:
1211 1211 #
1212 1212 # Par column: for the 2 parents with ...
1213 1213 # N: null or no parent
1214 1214 # B: parent is on another named branch
1215 1215 # C: parent is a regular non head changeset
1216 1216 # H: parent was a branch head of the current branch
1217 1217 # Msg column: whether we print "created new head" message
1218 1218 # In the following, it is assumed that there already exists some
1219 1219 # initial branch heads of the current branch, otherwise nothing is
1220 1220 # printed anyway.
1221 1221 #
1222 1222 # Par Msg Comment
1223 1223 # NN y additional topo root
1224 1224 #
1225 1225 # BN y additional branch root
1226 1226 # CN y additional topo head
1227 1227 # HN n usual case
1228 1228 #
1229 1229 # BB y weird additional branch root
1230 1230 # CB y branch merge
1231 1231 # HB n merge with named branch
1232 1232 #
1233 1233 # CC y additional head from merge
1234 1234 # CH n merge with a head
1235 1235 #
1236 1236 # HH n head merge: head count decreases
1237 1237
1238 1238 if not opts.get('close_branch'):
1239 1239 for r in parents:
1240 1240 if r.extra().get('close') and r.branch() == branch:
1241 1241 ui.status(_('reopening closed branch head %d\n') % r)
1242 1242
1243 1243 if ui.debugflag:
1244 1244 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1245 1245 elif ui.verbose:
1246 1246 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1247 1247
1248 1248 @command('copy|cp',
1249 1249 [('A', 'after', None, _('record a copy that has already occurred')),
1250 1250 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1251 1251 ] + walkopts + dryrunopts,
1252 1252 _('[OPTION]... [SOURCE]... DEST'))
1253 1253 def copy(ui, repo, *pats, **opts):
1254 1254 """mark files as copied for the next commit
1255 1255
1256 1256 Mark dest as having copies of source files. If dest is a
1257 1257 directory, copies are put in that directory. If dest is a file,
1258 1258 the source must be a single file.
1259 1259
1260 1260 By default, this command copies the contents of files as they
1261 1261 exist in the working directory. If invoked with -A/--after, the
1262 1262 operation is recorded, but no copying is performed.
1263 1263
1264 1264 This command takes effect with the next commit. To undo a copy
1265 1265 before that, see :hg:`revert`.
1266 1266
1267 1267 Returns 0 on success, 1 if errors are encountered.
1268 1268 """
1269 1269 wlock = repo.wlock(False)
1270 1270 try:
1271 1271 return cmdutil.copy(ui, repo, pats, opts)
1272 1272 finally:
1273 1273 wlock.release()
1274 1274
1275 1275 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1276 1276 def debugancestor(ui, repo, *args):
1277 1277 """find the ancestor revision of two revisions in a given index"""
1278 1278 if len(args) == 3:
1279 1279 index, rev1, rev2 = args
1280 1280 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1281 1281 lookup = r.lookup
1282 1282 elif len(args) == 2:
1283 1283 if not repo:
1284 1284 raise util.Abort(_("there is no Mercurial repository here "
1285 1285 "(.hg not found)"))
1286 1286 rev1, rev2 = args
1287 1287 r = repo.changelog
1288 1288 lookup = repo.lookup
1289 1289 else:
1290 1290 raise util.Abort(_('either two or three arguments required'))
1291 1291 a = r.ancestor(lookup(rev1), lookup(rev2))
1292 1292 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1293 1293
1294 1294 @command('debugbuilddag',
1295 1295 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1296 1296 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1297 1297 ('n', 'new-file', None, _('add new file at each rev'))],
1298 1298 _('[OPTION]... [TEXT]'))
1299 1299 def debugbuilddag(ui, repo, text=None,
1300 1300 mergeable_file=False,
1301 1301 overwritten_file=False,
1302 1302 new_file=False):
1303 1303 """builds a repo with a given DAG from scratch in the current empty repo
1304 1304
1305 1305 The description of the DAG is read from stdin if not given on the
1306 1306 command line.
1307 1307
1308 1308 Elements:
1309 1309
1310 1310 - "+n" is a linear run of n nodes based on the current default parent
1311 1311 - "." is a single node based on the current default parent
1312 1312 - "$" resets the default parent to null (implied at the start);
1313 1313 otherwise the default parent is always the last node created
1314 1314 - "<p" sets the default parent to the backref p
1315 1315 - "*p" is a fork at parent p, which is a backref
1316 1316 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1317 1317 - "/p2" is a merge of the preceding node and p2
1318 1318 - ":tag" defines a local tag for the preceding node
1319 1319 - "@branch" sets the named branch for subsequent nodes
1320 1320 - "#...\\n" is a comment up to the end of the line
1321 1321
1322 1322 Whitespace between the above elements is ignored.
1323 1323
1324 1324 A backref is either
1325 1325
1326 1326 - a number n, which references the node curr-n, where curr is the current
1327 1327 node, or
1328 1328 - the name of a local tag you placed earlier using ":tag", or
1329 1329 - empty to denote the default parent.
1330 1330
1331 1331 All string valued-elements are either strictly alphanumeric, or must
1332 1332 be enclosed in double quotes ("..."), with "\\" as escape character.
1333 1333 """
1334 1334
1335 1335 if text is None:
1336 1336 ui.status(_("reading DAG from stdin\n"))
1337 1337 text = ui.fin.read()
1338 1338
1339 1339 cl = repo.changelog
1340 1340 if len(cl) > 0:
1341 1341 raise util.Abort(_('repository is not empty'))
1342 1342
1343 1343 # determine number of revs in DAG
1344 1344 total = 0
1345 1345 for type, data in dagparser.parsedag(text):
1346 1346 if type == 'n':
1347 1347 total += 1
1348 1348
1349 1349 if mergeable_file:
1350 1350 linesperrev = 2
1351 1351 # make a file with k lines per rev
1352 1352 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1353 1353 initialmergedlines.append("")
1354 1354
1355 1355 tags = []
1356 1356
1357 1357 tr = repo.transaction("builddag")
1358 1358 try:
1359 1359
1360 1360 at = -1
1361 1361 atbranch = 'default'
1362 1362 nodeids = []
1363 1363 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1364 1364 for type, data in dagparser.parsedag(text):
1365 1365 if type == 'n':
1366 1366 ui.note('node %s\n' % str(data))
1367 1367 id, ps = data
1368 1368
1369 1369 files = []
1370 1370 fctxs = {}
1371 1371
1372 1372 p2 = None
1373 1373 if mergeable_file:
1374 1374 fn = "mf"
1375 1375 p1 = repo[ps[0]]
1376 1376 if len(ps) > 1:
1377 1377 p2 = repo[ps[1]]
1378 1378 pa = p1.ancestor(p2)
1379 1379 base, local, other = [x[fn].data() for x in pa, p1, p2]
1380 1380 m3 = simplemerge.Merge3Text(base, local, other)
1381 1381 ml = [l.strip() for l in m3.merge_lines()]
1382 1382 ml.append("")
1383 1383 elif at > 0:
1384 1384 ml = p1[fn].data().split("\n")
1385 1385 else:
1386 1386 ml = initialmergedlines
1387 1387 ml[id * linesperrev] += " r%i" % id
1388 1388 mergedtext = "\n".join(ml)
1389 1389 files.append(fn)
1390 1390 fctxs[fn] = context.memfilectx(fn, mergedtext)
1391 1391
1392 1392 if overwritten_file:
1393 1393 fn = "of"
1394 1394 files.append(fn)
1395 1395 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1396 1396
1397 1397 if new_file:
1398 1398 fn = "nf%i" % id
1399 1399 files.append(fn)
1400 1400 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1401 1401 if len(ps) > 1:
1402 1402 if not p2:
1403 1403 p2 = repo[ps[1]]
1404 1404 for fn in p2:
1405 1405 if fn.startswith("nf"):
1406 1406 files.append(fn)
1407 1407 fctxs[fn] = p2[fn]
1408 1408
1409 1409 def fctxfn(repo, cx, path):
1410 1410 return fctxs.get(path)
1411 1411
1412 1412 if len(ps) == 0 or ps[0] < 0:
1413 1413 pars = [None, None]
1414 1414 elif len(ps) == 1:
1415 1415 pars = [nodeids[ps[0]], None]
1416 1416 else:
1417 1417 pars = [nodeids[p] for p in ps]
1418 1418 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1419 1419 date=(id, 0),
1420 1420 user="debugbuilddag",
1421 1421 extra={'branch': atbranch})
1422 1422 nodeid = repo.commitctx(cx)
1423 1423 nodeids.append(nodeid)
1424 1424 at = id
1425 1425 elif type == 'l':
1426 1426 id, name = data
1427 1427 ui.note('tag %s\n' % name)
1428 1428 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1429 1429 elif type == 'a':
1430 1430 ui.note('branch %s\n' % data)
1431 1431 atbranch = data
1432 1432 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1433 1433 tr.close()
1434 1434 finally:
1435 1435 ui.progress(_('building'), None)
1436 1436 tr.release()
1437 1437
1438 1438 if tags:
1439 1439 repo.opener.write("localtags", "".join(tags))
1440 1440
1441 1441 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1442 1442 def debugbundle(ui, bundlepath, all=None, **opts):
1443 1443 """lists the contents of a bundle"""
1444 1444 f = url.open(ui, bundlepath)
1445 1445 try:
1446 1446 gen = changegroup.readbundle(f, bundlepath)
1447 1447 if all:
1448 1448 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1449 1449
1450 1450 def showchunks(named):
1451 1451 ui.write("\n%s\n" % named)
1452 1452 chain = None
1453 1453 while True:
1454 1454 chunkdata = gen.deltachunk(chain)
1455 1455 if not chunkdata:
1456 1456 break
1457 1457 node = chunkdata['node']
1458 1458 p1 = chunkdata['p1']
1459 1459 p2 = chunkdata['p2']
1460 1460 cs = chunkdata['cs']
1461 1461 deltabase = chunkdata['deltabase']
1462 1462 delta = chunkdata['delta']
1463 1463 ui.write("%s %s %s %s %s %s\n" %
1464 1464 (hex(node), hex(p1), hex(p2),
1465 1465 hex(cs), hex(deltabase), len(delta)))
1466 1466 chain = node
1467 1467
1468 1468 chunkdata = gen.changelogheader()
1469 1469 showchunks("changelog")
1470 1470 chunkdata = gen.manifestheader()
1471 1471 showchunks("manifest")
1472 1472 while True:
1473 1473 chunkdata = gen.filelogheader()
1474 1474 if not chunkdata:
1475 1475 break
1476 1476 fname = chunkdata['filename']
1477 1477 showchunks(fname)
1478 1478 else:
1479 1479 chunkdata = gen.changelogheader()
1480 1480 chain = None
1481 1481 while True:
1482 1482 chunkdata = gen.deltachunk(chain)
1483 1483 if not chunkdata:
1484 1484 break
1485 1485 node = chunkdata['node']
1486 1486 ui.write("%s\n" % hex(node))
1487 1487 chain = node
1488 1488 finally:
1489 1489 f.close()
1490 1490
1491 1491 @command('debugcheckstate', [], '')
1492 1492 def debugcheckstate(ui, repo):
1493 1493 """validate the correctness of the current dirstate"""
1494 1494 parent1, parent2 = repo.dirstate.parents()
1495 1495 m1 = repo[parent1].manifest()
1496 1496 m2 = repo[parent2].manifest()
1497 1497 errors = 0
1498 1498 for f in repo.dirstate:
1499 1499 state = repo.dirstate[f]
1500 1500 if state in "nr" and f not in m1:
1501 1501 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1502 1502 errors += 1
1503 1503 if state in "a" and f in m1:
1504 1504 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1505 1505 errors += 1
1506 1506 if state in "m" and f not in m1 and f not in m2:
1507 1507 ui.warn(_("%s in state %s, but not in either manifest\n") %
1508 1508 (f, state))
1509 1509 errors += 1
1510 1510 for f in m1:
1511 1511 state = repo.dirstate[f]
1512 1512 if state not in "nrm":
1513 1513 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1514 1514 errors += 1
1515 1515 if errors:
1516 1516 error = _(".hg/dirstate inconsistent with current parent's manifest")
1517 1517 raise util.Abort(error)
1518 1518
1519 1519 @command('debugcommands', [], _('[COMMAND]'))
1520 1520 def debugcommands(ui, cmd='', *args):
1521 1521 """list all available commands and options"""
1522 1522 for cmd, vals in sorted(table.iteritems()):
1523 1523 cmd = cmd.split('|')[0].strip('^')
1524 1524 opts = ', '.join([i[1] for i in vals[1]])
1525 1525 ui.write('%s: %s\n' % (cmd, opts))
1526 1526
1527 1527 @command('debugcomplete',
1528 1528 [('o', 'options', None, _('show the command options'))],
1529 1529 _('[-o] CMD'))
1530 1530 def debugcomplete(ui, cmd='', **opts):
1531 1531 """returns the completion list associated with the given command"""
1532 1532
1533 1533 if opts.get('options'):
1534 1534 options = []
1535 1535 otables = [globalopts]
1536 1536 if cmd:
1537 1537 aliases, entry = cmdutil.findcmd(cmd, table, False)
1538 1538 otables.append(entry[1])
1539 1539 for t in otables:
1540 1540 for o in t:
1541 1541 if "(DEPRECATED)" in o[3]:
1542 1542 continue
1543 1543 if o[0]:
1544 1544 options.append('-%s' % o[0])
1545 1545 options.append('--%s' % o[1])
1546 1546 ui.write("%s\n" % "\n".join(options))
1547 1547 return
1548 1548
1549 1549 cmdlist = cmdutil.findpossible(cmd, table)
1550 1550 if ui.verbose:
1551 1551 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1552 1552 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1553 1553
1554 1554 @command('debugdag',
1555 1555 [('t', 'tags', None, _('use tags as labels')),
1556 1556 ('b', 'branches', None, _('annotate with branch names')),
1557 1557 ('', 'dots', None, _('use dots for runs')),
1558 1558 ('s', 'spaces', None, _('separate elements by spaces'))],
1559 1559 _('[OPTION]... [FILE [REV]...]'))
1560 1560 def debugdag(ui, repo, file_=None, *revs, **opts):
1561 1561 """format the changelog or an index DAG as a concise textual description
1562 1562
1563 1563 If you pass a revlog index, the revlog's DAG is emitted. If you list
1564 1564 revision numbers, they get labelled in the output as rN.
1565 1565
1566 1566 Otherwise, the changelog DAG of the current repo is emitted.
1567 1567 """
1568 1568 spaces = opts.get('spaces')
1569 1569 dots = opts.get('dots')
1570 1570 if file_:
1571 1571 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1572 1572 revs = set((int(r) for r in revs))
1573 1573 def events():
1574 1574 for r in rlog:
1575 1575 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1576 1576 if r in revs:
1577 1577 yield 'l', (r, "r%i" % r)
1578 1578 elif repo:
1579 1579 cl = repo.changelog
1580 1580 tags = opts.get('tags')
1581 1581 branches = opts.get('branches')
1582 1582 if tags:
1583 1583 labels = {}
1584 1584 for l, n in repo.tags().items():
1585 1585 labels.setdefault(cl.rev(n), []).append(l)
1586 1586 def events():
1587 1587 b = "default"
1588 1588 for r in cl:
1589 1589 if branches:
1590 1590 newb = cl.read(cl.node(r))[5]['branch']
1591 1591 if newb != b:
1592 1592 yield 'a', newb
1593 1593 b = newb
1594 1594 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1595 1595 if tags:
1596 1596 ls = labels.get(r)
1597 1597 if ls:
1598 1598 for l in ls:
1599 1599 yield 'l', (r, l)
1600 1600 else:
1601 1601 raise util.Abort(_('need repo for changelog dag'))
1602 1602
1603 1603 for line in dagparser.dagtextlines(events(),
1604 1604 addspaces=spaces,
1605 1605 wraplabels=True,
1606 1606 wrapannotations=True,
1607 1607 wrapnonlinear=dots,
1608 1608 usedots=dots,
1609 1609 maxlinewidth=70):
1610 1610 ui.write(line)
1611 1611 ui.write("\n")
1612 1612
1613 1613 @command('debugdata',
1614 1614 [('c', 'changelog', False, _('open changelog')),
1615 1615 ('m', 'manifest', False, _('open manifest'))],
1616 1616 _('-c|-m|FILE REV'))
1617 1617 def debugdata(ui, repo, file_, rev = None, **opts):
1618 1618 """dump the contents of a data file revision"""
1619 1619 if opts.get('changelog') or opts.get('manifest'):
1620 1620 file_, rev = None, file_
1621 1621 elif rev is None:
1622 1622 raise error.CommandError('debugdata', _('invalid arguments'))
1623 1623 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1624 1624 try:
1625 1625 ui.write(r.revision(r.lookup(rev)))
1626 1626 except KeyError:
1627 1627 raise util.Abort(_('invalid revision identifier %s') % rev)
1628 1628
1629 1629 @command('debugdate',
1630 1630 [('e', 'extended', None, _('try extended date formats'))],
1631 1631 _('[-e] DATE [RANGE]'))
1632 1632 def debugdate(ui, date, range=None, **opts):
1633 1633 """parse and display a date"""
1634 1634 if opts["extended"]:
1635 1635 d = util.parsedate(date, util.extendeddateformats)
1636 1636 else:
1637 1637 d = util.parsedate(date)
1638 1638 ui.write("internal: %s %s\n" % d)
1639 1639 ui.write("standard: %s\n" % util.datestr(d))
1640 1640 if range:
1641 1641 m = util.matchdate(range)
1642 1642 ui.write("match: %s\n" % m(d[0]))
1643 1643
1644 1644 @command('debugdiscovery',
1645 1645 [('', 'old', None, _('use old-style discovery')),
1646 1646 ('', 'nonheads', None,
1647 1647 _('use old-style discovery with non-heads included')),
1648 1648 ] + remoteopts,
1649 1649 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1650 1650 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1651 1651 """runs the changeset discovery protocol in isolation"""
1652 1652 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1653 1653 remote = hg.peer(repo, opts, remoteurl)
1654 1654 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1655 1655
1656 1656 # make sure tests are repeatable
1657 1657 random.seed(12323)
1658 1658
1659 1659 def doit(localheads, remoteheads):
1660 1660 if opts.get('old'):
1661 1661 if localheads:
1662 1662 raise util.Abort('cannot use localheads with old style discovery')
1663 1663 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1664 1664 force=True)
1665 1665 common = set(common)
1666 1666 if not opts.get('nonheads'):
1667 1667 ui.write("unpruned common: %s\n" % " ".join([short(n)
1668 1668 for n in common]))
1669 1669 dag = dagutil.revlogdag(repo.changelog)
1670 1670 all = dag.ancestorset(dag.internalizeall(common))
1671 1671 common = dag.externalizeall(dag.headsetofconnecteds(all))
1672 1672 else:
1673 1673 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1674 1674 common = set(common)
1675 1675 rheads = set(hds)
1676 1676 lheads = set(repo.heads())
1677 1677 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1678 1678 if lheads <= common:
1679 1679 ui.write("local is subset\n")
1680 1680 elif rheads <= common:
1681 1681 ui.write("remote is subset\n")
1682 1682
1683 1683 serverlogs = opts.get('serverlog')
1684 1684 if serverlogs:
1685 1685 for filename in serverlogs:
1686 1686 logfile = open(filename, 'r')
1687 1687 try:
1688 1688 line = logfile.readline()
1689 1689 while line:
1690 1690 parts = line.strip().split(';')
1691 1691 op = parts[1]
1692 1692 if op == 'cg':
1693 1693 pass
1694 1694 elif op == 'cgss':
1695 1695 doit(parts[2].split(' '), parts[3].split(' '))
1696 1696 elif op == 'unb':
1697 1697 doit(parts[3].split(' '), parts[2].split(' '))
1698 1698 line = logfile.readline()
1699 1699 finally:
1700 1700 logfile.close()
1701 1701
1702 1702 else:
1703 1703 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1704 1704 opts.get('remote_head'))
1705 1705 localrevs = opts.get('local_head')
1706 1706 doit(localrevs, remoterevs)
1707 1707
1708 1708 @command('debugfileset', [], ('REVSPEC'))
1709 1709 def debugfileset(ui, repo, expr):
1710 1710 '''parse and apply a fileset specification'''
1711 1711 if ui.verbose:
1712 1712 tree = fileset.parse(expr)[0]
1713 1713 ui.note(tree, "\n")
1714 1714
1715 1715 for f in fileset.getfileset(repo[None], expr):
1716 1716 ui.write("%s\n" % f)
1717 1717
1718 1718 @command('debugfsinfo', [], _('[PATH]'))
1719 1719 def debugfsinfo(ui, path = "."):
1720 1720 """show information detected about current filesystem"""
1721 1721 util.writefile('.debugfsinfo', '')
1722 1722 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1723 1723 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1724 1724 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1725 1725 and 'yes' or 'no'))
1726 1726 os.unlink('.debugfsinfo')
1727 1727
1728 1728 @command('debuggetbundle',
1729 1729 [('H', 'head', [], _('id of head node'), _('ID')),
1730 1730 ('C', 'common', [], _('id of common node'), _('ID')),
1731 1731 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1732 1732 _('REPO FILE [-H|-C ID]...'))
1733 1733 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1734 1734 """retrieves a bundle from a repo
1735 1735
1736 1736 Every ID must be a full-length hex node id string. Saves the bundle to the
1737 1737 given file.
1738 1738 """
1739 1739 repo = hg.peer(ui, opts, repopath)
1740 1740 if not repo.capable('getbundle'):
1741 1741 raise util.Abort("getbundle() not supported by target repository")
1742 1742 args = {}
1743 1743 if common:
1744 1744 args['common'] = [bin(s) for s in common]
1745 1745 if head:
1746 1746 args['heads'] = [bin(s) for s in head]
1747 1747 bundle = repo.getbundle('debug', **args)
1748 1748
1749 1749 bundletype = opts.get('type', 'bzip2').lower()
1750 1750 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1751 1751 bundletype = btypes.get(bundletype)
1752 1752 if bundletype not in changegroup.bundletypes:
1753 1753 raise util.Abort(_('unknown bundle type specified with --type'))
1754 1754 changegroup.writebundle(bundle, bundlepath, bundletype)
1755 1755
1756 1756 @command('debugignore', [], '')
1757 1757 def debugignore(ui, repo, *values, **opts):
1758 1758 """display the combined ignore pattern"""
1759 1759 ignore = repo.dirstate._ignore
1760 1760 includepat = getattr(ignore, 'includepat', None)
1761 1761 if includepat is not None:
1762 1762 ui.write("%s\n" % includepat)
1763 1763 else:
1764 1764 raise util.Abort(_("no ignore patterns found"))
1765 1765
1766 1766 @command('debugindex',
1767 1767 [('c', 'changelog', False, _('open changelog')),
1768 1768 ('m', 'manifest', False, _('open manifest')),
1769 1769 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1770 1770 _('[-f FORMAT] -c|-m|FILE'))
1771 1771 def debugindex(ui, repo, file_ = None, **opts):
1772 1772 """dump the contents of an index file"""
1773 1773 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1774 1774 format = opts.get('format', 0)
1775 1775 if format not in (0, 1):
1776 1776 raise util.Abort(_("unknown format %d") % format)
1777 1777
1778 1778 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1779 1779 if generaldelta:
1780 1780 basehdr = ' delta'
1781 1781 else:
1782 1782 basehdr = ' base'
1783 1783
1784 1784 if format == 0:
1785 1785 ui.write(" rev offset length " + basehdr + " linkrev"
1786 1786 " nodeid p1 p2\n")
1787 1787 elif format == 1:
1788 1788 ui.write(" rev flag offset length"
1789 1789 " size " + basehdr + " link p1 p2 nodeid\n")
1790 1790
1791 1791 for i in r:
1792 1792 node = r.node(i)
1793 1793 if generaldelta:
1794 1794 base = r.deltaparent(i)
1795 1795 else:
1796 1796 base = r.chainbase(i)
1797 1797 if format == 0:
1798 1798 try:
1799 1799 pp = r.parents(node)
1800 1800 except:
1801 1801 pp = [nullid, nullid]
1802 1802 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1803 1803 i, r.start(i), r.length(i), base, r.linkrev(i),
1804 1804 short(node), short(pp[0]), short(pp[1])))
1805 1805 elif format == 1:
1806 1806 pr = r.parentrevs(i)
1807 1807 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1808 1808 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1809 1809 base, r.linkrev(i), pr[0], pr[1], short(node)))
1810 1810
1811 1811 @command('debugindexdot', [], _('FILE'))
1812 1812 def debugindexdot(ui, repo, file_):
1813 1813 """dump an index DAG as a graphviz dot file"""
1814 1814 r = None
1815 1815 if repo:
1816 1816 filelog = repo.file(file_)
1817 1817 if len(filelog):
1818 1818 r = filelog
1819 1819 if not r:
1820 1820 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1821 1821 ui.write("digraph G {\n")
1822 1822 for i in r:
1823 1823 node = r.node(i)
1824 1824 pp = r.parents(node)
1825 1825 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1826 1826 if pp[1] != nullid:
1827 1827 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1828 1828 ui.write("}\n")
1829 1829
1830 1830 @command('debuginstall', [], '')
1831 1831 def debuginstall(ui):
1832 1832 '''test Mercurial installation
1833 1833
1834 1834 Returns 0 on success.
1835 1835 '''
1836 1836
1837 1837 def writetemp(contents):
1838 1838 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1839 1839 f = os.fdopen(fd, "wb")
1840 1840 f.write(contents)
1841 1841 f.close()
1842 1842 return name
1843 1843
1844 1844 problems = 0
1845 1845
1846 1846 # encoding
1847 1847 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1848 1848 try:
1849 1849 encoding.fromlocal("test")
1850 1850 except util.Abort, inst:
1851 1851 ui.write(" %s\n" % inst)
1852 1852 ui.write(_(" (check that your locale is properly set)\n"))
1853 1853 problems += 1
1854 1854
1855 1855 # compiled modules
1856 1856 ui.status(_("Checking installed modules (%s)...\n")
1857 1857 % os.path.dirname(__file__))
1858 1858 try:
1859 1859 import bdiff, mpatch, base85, osutil
1860 1860 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1861 1861 except Exception, inst:
1862 1862 ui.write(" %s\n" % inst)
1863 1863 ui.write(_(" One or more extensions could not be found"))
1864 1864 ui.write(_(" (check that you compiled the extensions)\n"))
1865 1865 problems += 1
1866 1866
1867 1867 # templates
1868 1868 import templater
1869 1869 p = templater.templatepath()
1870 1870 ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
1871 1871 try:
1872 1872 templater.templater(templater.templatepath("map-cmdline.default"))
1873 1873 except Exception, inst:
1874 1874 ui.write(" %s\n" % inst)
1875 1875 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1876 1876 problems += 1
1877 1877
1878 1878 # editor
1879 1879 ui.status(_("Checking commit editor...\n"))
1880 1880 editor = ui.geteditor()
1881 1881 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1882 1882 if not cmdpath:
1883 1883 if editor == 'vi':
1884 1884 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1885 1885 ui.write(_(" (specify a commit editor in your configuration"
1886 1886 " file)\n"))
1887 1887 else:
1888 1888 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1889 1889 ui.write(_(" (specify a commit editor in your configuration"
1890 1890 " file)\n"))
1891 1891 problems += 1
1892 1892
1893 1893 # check username
1894 1894 ui.status(_("Checking username...\n"))
1895 1895 try:
1896 1896 ui.username()
1897 1897 except util.Abort, e:
1898 1898 ui.write(" %s\n" % e)
1899 1899 ui.write(_(" (specify a username in your configuration file)\n"))
1900 1900 problems += 1
1901 1901
1902 1902 if not problems:
1903 1903 ui.status(_("No problems detected\n"))
1904 1904 else:
1905 1905 ui.write(_("%s problems detected,"
1906 1906 " please check your install!\n") % problems)
1907 1907
1908 1908 return problems
1909 1909
1910 1910 @command('debugknown', [], _('REPO ID...'))
1911 1911 def debugknown(ui, repopath, *ids, **opts):
1912 1912 """test whether node ids are known to a repo
1913 1913
1914 1914 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1915 1915 indicating unknown/known.
1916 1916 """
1917 1917 repo = hg.peer(ui, opts, repopath)
1918 1918 if not repo.capable('known'):
1919 1919 raise util.Abort("known() not supported by target repository")
1920 1920 flags = repo.known([bin(s) for s in ids])
1921 1921 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1922 1922
1923 1923 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1924 1924 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1925 1925 '''access the pushkey key/value protocol
1926 1926
1927 1927 With two args, list the keys in the given namespace.
1928 1928
1929 1929 With five args, set a key to new if it currently is set to old.
1930 1930 Reports success or failure.
1931 1931 '''
1932 1932
1933 1933 target = hg.peer(ui, {}, repopath)
1934 1934 if keyinfo:
1935 1935 key, old, new = keyinfo
1936 1936 r = target.pushkey(namespace, key, old, new)
1937 1937 ui.status(str(r) + '\n')
1938 1938 return not r
1939 1939 else:
1940 1940 for k, v in target.listkeys(namespace).iteritems():
1941 1941 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1942 1942 v.encode('string-escape')))
1943 1943
1944 1944 @command('debugrebuildstate',
1945 1945 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1946 1946 _('[-r REV] [REV]'))
1947 1947 def debugrebuildstate(ui, repo, rev="tip"):
1948 1948 """rebuild the dirstate as it would look like for the given revision"""
1949 1949 ctx = scmutil.revsingle(repo, rev)
1950 1950 wlock = repo.wlock()
1951 1951 try:
1952 1952 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1953 1953 finally:
1954 1954 wlock.release()
1955 1955
1956 1956 @command('debugrename',
1957 1957 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1958 1958 _('[-r REV] FILE'))
1959 1959 def debugrename(ui, repo, file1, *pats, **opts):
1960 1960 """dump rename information"""
1961 1961
1962 1962 ctx = scmutil.revsingle(repo, opts.get('rev'))
1963 1963 m = scmutil.match(ctx, (file1,) + pats, opts)
1964 1964 for abs in ctx.walk(m):
1965 1965 fctx = ctx[abs]
1966 1966 o = fctx.filelog().renamed(fctx.filenode())
1967 1967 rel = m.rel(abs)
1968 1968 if o:
1969 1969 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1970 1970 else:
1971 1971 ui.write(_("%s not renamed\n") % rel)
1972 1972
1973 1973 @command('debugrevlog',
1974 1974 [('c', 'changelog', False, _('open changelog')),
1975 1975 ('m', 'manifest', False, _('open manifest')),
1976 1976 ('d', 'dump', False, _('dump index data'))],
1977 1977 _('-c|-m|FILE'))
1978 1978 def debugrevlog(ui, repo, file_ = None, **opts):
1979 1979 """show data and statistics about a revlog"""
1980 1980 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1981 1981
1982 1982 if opts.get("dump"):
1983 1983 numrevs = len(r)
1984 1984 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1985 1985 " rawsize totalsize compression heads\n")
1986 1986 ts = 0
1987 1987 heads = set()
1988 1988 for rev in xrange(numrevs):
1989 1989 dbase = r.deltaparent(rev)
1990 1990 if dbase == -1:
1991 1991 dbase = rev
1992 1992 cbase = r.chainbase(rev)
1993 1993 p1, p2 = r.parentrevs(rev)
1994 1994 rs = r.rawsize(rev)
1995 1995 ts = ts + rs
1996 1996 heads -= set(r.parentrevs(rev))
1997 1997 heads.add(rev)
1998 1998 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
1999 1999 (rev, p1, p2, r.start(rev), r.end(rev),
2000 2000 r.start(dbase), r.start(cbase),
2001 2001 r.start(p1), r.start(p2),
2002 2002 rs, ts, ts / r.end(rev), len(heads)))
2003 2003 return 0
2004 2004
2005 2005 v = r.version
2006 2006 format = v & 0xFFFF
2007 2007 flags = []
2008 2008 gdelta = False
2009 2009 if v & revlog.REVLOGNGINLINEDATA:
2010 2010 flags.append('inline')
2011 2011 if v & revlog.REVLOGGENERALDELTA:
2012 2012 gdelta = True
2013 2013 flags.append('generaldelta')
2014 2014 if not flags:
2015 2015 flags = ['(none)']
2016 2016
2017 2017 nummerges = 0
2018 2018 numfull = 0
2019 2019 numprev = 0
2020 2020 nump1 = 0
2021 2021 nump2 = 0
2022 2022 numother = 0
2023 2023 nump1prev = 0
2024 2024 nump2prev = 0
2025 2025 chainlengths = []
2026 2026
2027 2027 datasize = [None, 0, 0L]
2028 2028 fullsize = [None, 0, 0L]
2029 2029 deltasize = [None, 0, 0L]
2030 2030
2031 2031 def addsize(size, l):
2032 2032 if l[0] is None or size < l[0]:
2033 2033 l[0] = size
2034 2034 if size > l[1]:
2035 2035 l[1] = size
2036 2036 l[2] += size
2037 2037
2038 2038 numrevs = len(r)
2039 2039 for rev in xrange(numrevs):
2040 2040 p1, p2 = r.parentrevs(rev)
2041 2041 delta = r.deltaparent(rev)
2042 2042 if format > 0:
2043 2043 addsize(r.rawsize(rev), datasize)
2044 2044 if p2 != nullrev:
2045 2045 nummerges += 1
2046 2046 size = r.length(rev)
2047 2047 if delta == nullrev:
2048 2048 chainlengths.append(0)
2049 2049 numfull += 1
2050 2050 addsize(size, fullsize)
2051 2051 else:
2052 2052 chainlengths.append(chainlengths[delta] + 1)
2053 2053 addsize(size, deltasize)
2054 2054 if delta == rev - 1:
2055 2055 numprev += 1
2056 2056 if delta == p1:
2057 2057 nump1prev += 1
2058 2058 elif delta == p2:
2059 2059 nump2prev += 1
2060 2060 elif delta == p1:
2061 2061 nump1 += 1
2062 2062 elif delta == p2:
2063 2063 nump2 += 1
2064 2064 elif delta != nullrev:
2065 2065 numother += 1
2066 2066
2067 2067 numdeltas = numrevs - numfull
2068 2068 numoprev = numprev - nump1prev - nump2prev
2069 2069 totalrawsize = datasize[2]
2070 2070 datasize[2] /= numrevs
2071 2071 fulltotal = fullsize[2]
2072 2072 fullsize[2] /= numfull
2073 2073 deltatotal = deltasize[2]
2074 2074 deltasize[2] /= numrevs - numfull
2075 2075 totalsize = fulltotal + deltatotal
2076 2076 avgchainlen = sum(chainlengths) / numrevs
2077 2077 compratio = totalrawsize / totalsize
2078 2078
2079 2079 basedfmtstr = '%%%dd\n'
2080 2080 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2081 2081
2082 2082 def dfmtstr(max):
2083 2083 return basedfmtstr % len(str(max))
2084 2084 def pcfmtstr(max, padding=0):
2085 2085 return basepcfmtstr % (len(str(max)), ' ' * padding)
2086 2086
2087 2087 def pcfmt(value, total):
2088 2088 return (value, 100 * float(value) / total)
2089 2089
2090 2090 ui.write('format : %d\n' % format)
2091 2091 ui.write('flags : %s\n' % ', '.join(flags))
2092 2092
2093 2093 ui.write('\n')
2094 2094 fmt = pcfmtstr(totalsize)
2095 2095 fmt2 = dfmtstr(totalsize)
2096 2096 ui.write('revisions : ' + fmt2 % numrevs)
2097 2097 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2098 2098 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2099 2099 ui.write('revisions : ' + fmt2 % numrevs)
2100 2100 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2101 2101 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2102 2102 ui.write('revision size : ' + fmt2 % totalsize)
2103 2103 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2104 2104 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2105 2105
2106 2106 ui.write('\n')
2107 2107 fmt = dfmtstr(max(avgchainlen, compratio))
2108 2108 ui.write('avg chain length : ' + fmt % avgchainlen)
2109 2109 ui.write('compression ratio : ' + fmt % compratio)
2110 2110
2111 2111 if format > 0:
2112 2112 ui.write('\n')
2113 2113 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2114 2114 % tuple(datasize))
2115 2115 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2116 2116 % tuple(fullsize))
2117 2117 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2118 2118 % tuple(deltasize))
2119 2119
2120 2120 if numdeltas > 0:
2121 2121 ui.write('\n')
2122 2122 fmt = pcfmtstr(numdeltas)
2123 2123 fmt2 = pcfmtstr(numdeltas, 4)
2124 2124 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2125 2125 if numprev > 0:
2126 2126 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2127 2127 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2128 2128 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2129 2129 if gdelta:
2130 2130 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2131 2131 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2132 2132 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2133 2133
2134 2134 @command('debugrevspec', [], ('REVSPEC'))
2135 2135 def debugrevspec(ui, repo, expr):
2136 2136 '''parse and apply a revision specification'''
2137 2137 if ui.verbose:
2138 2138 tree = revset.parse(expr)[0]
2139 2139 ui.note(tree, "\n")
2140 2140 newtree = revset.findaliases(ui, tree)
2141 2141 if newtree != tree:
2142 2142 ui.note(newtree, "\n")
2143 2143 func = revset.match(ui, expr)
2144 2144 for c in func(repo, range(len(repo))):
2145 2145 ui.write("%s\n" % c)
2146 2146
2147 2147 @command('debugsetparents', [], _('REV1 [REV2]'))
2148 2148 def debugsetparents(ui, repo, rev1, rev2=None):
2149 2149 """manually set the parents of the current working directory
2150 2150
2151 2151 This is useful for writing repository conversion tools, but should
2152 2152 be used with care.
2153 2153
2154 2154 Returns 0 on success.
2155 2155 """
2156 2156
2157 2157 r1 = scmutil.revsingle(repo, rev1).node()
2158 2158 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2159 2159
2160 2160 wlock = repo.wlock()
2161 2161 try:
2162 2162 repo.dirstate.setparents(r1, r2)
2163 2163 finally:
2164 2164 wlock.release()
2165 2165
2166 2166 @command('debugstate',
2167 2167 [('', 'nodates', None, _('do not display the saved mtime')),
2168 2168 ('', 'datesort', None, _('sort by saved mtime'))],
2169 2169 _('[OPTION]...'))
2170 2170 def debugstate(ui, repo, nodates=None, datesort=None):
2171 2171 """show the contents of the current dirstate"""
2172 2172 timestr = ""
2173 2173 showdate = not nodates
2174 2174 if datesort:
2175 2175 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2176 2176 else:
2177 2177 keyfunc = None # sort by filename
2178 2178 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2179 2179 if showdate:
2180 2180 if ent[3] == -1:
2181 2181 # Pad or slice to locale representation
2182 2182 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2183 2183 time.localtime(0)))
2184 2184 timestr = 'unset'
2185 2185 timestr = (timestr[:locale_len] +
2186 2186 ' ' * (locale_len - len(timestr)))
2187 2187 else:
2188 2188 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2189 2189 time.localtime(ent[3]))
2190 2190 if ent[1] & 020000:
2191 2191 mode = 'lnk'
2192 2192 else:
2193 2193 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2194 2194 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2195 2195 for f in repo.dirstate.copies():
2196 2196 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2197 2197
2198 2198 @command('debugsub',
2199 2199 [('r', 'rev', '',
2200 2200 _('revision to check'), _('REV'))],
2201 2201 _('[-r REV] [REV]'))
2202 2202 def debugsub(ui, repo, rev=None):
2203 2203 ctx = scmutil.revsingle(repo, rev, None)
2204 2204 for k, v in sorted(ctx.substate.items()):
2205 2205 ui.write('path %s\n' % k)
2206 2206 ui.write(' source %s\n' % v[0])
2207 2207 ui.write(' revision %s\n' % v[1])
2208 2208
2209 2209 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2210 2210 def debugwalk(ui, repo, *pats, **opts):
2211 2211 """show how files match on given patterns"""
2212 2212 m = scmutil.match(repo[None], pats, opts)
2213 2213 items = list(repo.walk(m))
2214 2214 if not items:
2215 2215 return
2216 2216 fmt = 'f %%-%ds %%-%ds %%s' % (
2217 2217 max([len(abs) for abs in items]),
2218 2218 max([len(m.rel(abs)) for abs in items]))
2219 2219 for abs in items:
2220 2220 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2221 2221 ui.write("%s\n" % line.rstrip())
2222 2222
2223 2223 @command('debugwireargs',
2224 2224 [('', 'three', '', 'three'),
2225 2225 ('', 'four', '', 'four'),
2226 2226 ('', 'five', '', 'five'),
2227 2227 ] + remoteopts,
2228 2228 _('REPO [OPTIONS]... [ONE [TWO]]'))
2229 2229 def debugwireargs(ui, repopath, *vals, **opts):
2230 2230 repo = hg.peer(ui, opts, repopath)
2231 2231 for opt in remoteopts:
2232 2232 del opts[opt[1]]
2233 2233 args = {}
2234 2234 for k, v in opts.iteritems():
2235 2235 if v:
2236 2236 args[k] = v
2237 2237 # run twice to check that we don't mess up the stream for the next command
2238 2238 res1 = repo.debugwireargs(*vals, **args)
2239 2239 res2 = repo.debugwireargs(*vals, **args)
2240 2240 ui.write("%s\n" % res1)
2241 2241 if res1 != res2:
2242 2242 ui.warn("%s\n" % res2)
2243 2243
2244 2244 @command('^diff',
2245 2245 [('r', 'rev', [], _('revision'), _('REV')),
2246 2246 ('c', 'change', '', _('change made by revision'), _('REV'))
2247 2247 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2248 2248 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2249 2249 def diff(ui, repo, *pats, **opts):
2250 2250 """diff repository (or selected files)
2251 2251
2252 2252 Show differences between revisions for the specified files.
2253 2253
2254 2254 Differences between files are shown using the unified diff format.
2255 2255
2256 2256 .. note::
2257 2257 diff may generate unexpected results for merges, as it will
2258 2258 default to comparing against the working directory's first
2259 2259 parent changeset if no revisions are specified.
2260 2260
2261 2261 When two revision arguments are given, then changes are shown
2262 2262 between those revisions. If only one revision is specified then
2263 2263 that revision is compared to the working directory, and, when no
2264 2264 revisions are specified, the working directory files are compared
2265 2265 to its parent.
2266 2266
2267 2267 Alternatively you can specify -c/--change with a revision to see
2268 2268 the changes in that changeset relative to its first parent.
2269 2269
2270 2270 Without the -a/--text option, diff will avoid generating diffs of
2271 2271 files it detects as binary. With -a, diff will generate a diff
2272 2272 anyway, probably with undesirable results.
2273 2273
2274 2274 Use the -g/--git option to generate diffs in the git extended diff
2275 2275 format. For more information, read :hg:`help diffs`.
2276 2276
2277 2277 .. container:: verbose
2278 2278
2279 2279 Examples:
2280 2280
2281 2281 - compare a file in the current working directory to its parent::
2282 2282
2283 2283 hg diff foo.c
2284 2284
2285 2285 - compare two historical versions of a directory, with rename info::
2286 2286
2287 2287 hg diff --git -r 1.0:1.2 lib/
2288 2288
2289 2289 - get change stats relative to the last change on some date::
2290 2290
2291 2291 hg diff --stat -r "date('may 2')"
2292 2292
2293 2293 - diff all newly-added files that contain a keyword::
2294 2294
2295 2295 hg diff "set:added() and grep(GNU)"
2296 2296
2297 2297 - compare a revision and its parents::
2298 2298
2299 2299 hg diff -c 9353 # compare against first parent
2300 2300 hg diff -r 9353^:9353 # same using revset syntax
2301 2301 hg diff -r 9353^2:9353 # compare against the second parent
2302 2302
2303 2303 Returns 0 on success.
2304 2304 """
2305 2305
2306 2306 revs = opts.get('rev')
2307 2307 change = opts.get('change')
2308 2308 stat = opts.get('stat')
2309 2309 reverse = opts.get('reverse')
2310 2310
2311 2311 if revs and change:
2312 2312 msg = _('cannot specify --rev and --change at the same time')
2313 2313 raise util.Abort(msg)
2314 2314 elif change:
2315 2315 node2 = scmutil.revsingle(repo, change, None).node()
2316 2316 node1 = repo[node2].p1().node()
2317 2317 else:
2318 2318 node1, node2 = scmutil.revpair(repo, revs)
2319 2319
2320 2320 if reverse:
2321 2321 node1, node2 = node2, node1
2322 2322
2323 2323 diffopts = patch.diffopts(ui, opts)
2324 2324 m = scmutil.match(repo[node2], pats, opts)
2325 2325 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2326 2326 listsubrepos=opts.get('subrepos'))
2327 2327
2328 2328 @command('^export',
2329 2329 [('o', 'output', '',
2330 2330 _('print output to file with formatted name'), _('FORMAT')),
2331 2331 ('', 'switch-parent', None, _('diff against the second parent')),
2332 2332 ('r', 'rev', [], _('revisions to export'), _('REV')),
2333 2333 ] + diffopts,
2334 2334 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2335 2335 def export(ui, repo, *changesets, **opts):
2336 2336 """dump the header and diffs for one or more changesets
2337 2337
2338 2338 Print the changeset header and diffs for one or more revisions.
2339 2339
2340 2340 The information shown in the changeset header is: author, date,
2341 2341 branch name (if non-default), changeset hash, parent(s) and commit
2342 2342 comment.
2343 2343
2344 2344 .. note::
2345 2345 export may generate unexpected diff output for merge
2346 2346 changesets, as it will compare the merge changeset against its
2347 2347 first parent only.
2348 2348
2349 2349 Output may be to a file, in which case the name of the file is
2350 2350 given using a format string. The formatting rules are as follows:
2351 2351
2352 2352 :``%%``: literal "%" character
2353 2353 :``%H``: changeset hash (40 hexadecimal digits)
2354 2354 :``%N``: number of patches being generated
2355 2355 :``%R``: changeset revision number
2356 2356 :``%b``: basename of the exporting repository
2357 2357 :``%h``: short-form changeset hash (12 hexadecimal digits)
2358 2358 :``%m``: first line of the commit message (only alphanumeric characters)
2359 2359 :``%n``: zero-padded sequence number, starting at 1
2360 2360 :``%r``: zero-padded changeset revision number
2361 2361
2362 2362 Without the -a/--text option, export will avoid generating diffs
2363 2363 of files it detects as binary. With -a, export will generate a
2364 2364 diff anyway, probably with undesirable results.
2365 2365
2366 2366 Use the -g/--git option to generate diffs in the git extended diff
2367 2367 format. See :hg:`help diffs` for more information.
2368 2368
2369 2369 With the --switch-parent option, the diff will be against the
2370 2370 second parent. It can be useful to review a merge.
2371 2371
2372 2372 .. container:: verbose
2373 2373
2374 2374 Examples:
2375 2375
2376 2376 - use export and import to transplant a bugfix to the current
2377 2377 branch::
2378 2378
2379 2379 hg export -r 9353 | hg import -
2380 2380
2381 2381 - export all the changesets between two revisions to a file with
2382 2382 rename information::
2383 2383
2384 2384 hg export --git -r 123:150 > changes.txt
2385 2385
2386 2386 - split outgoing changes into a series of patches with
2387 2387 descriptive names::
2388 2388
2389 2389 hg export -r "outgoing()" -o "%n-%m.patch"
2390 2390
2391 2391 Returns 0 on success.
2392 2392 """
2393 2393 changesets += tuple(opts.get('rev', []))
2394 2394 if not changesets:
2395 2395 raise util.Abort(_("export requires at least one changeset"))
2396 2396 revs = scmutil.revrange(repo, changesets)
2397 2397 if len(revs) > 1:
2398 2398 ui.note(_('exporting patches:\n'))
2399 2399 else:
2400 2400 ui.note(_('exporting patch:\n'))
2401 2401 cmdutil.export(repo, revs, template=opts.get('output'),
2402 2402 switch_parent=opts.get('switch_parent'),
2403 2403 opts=patch.diffopts(ui, opts))
2404 2404
2405 2405 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2406 2406 def forget(ui, repo, *pats, **opts):
2407 2407 """forget the specified files on the next commit
2408 2408
2409 2409 Mark the specified files so they will no longer be tracked
2410 2410 after the next commit.
2411 2411
2412 2412 This only removes files from the current branch, not from the
2413 2413 entire project history, and it does not delete them from the
2414 2414 working directory.
2415 2415
2416 2416 To undo a forget before the next commit, see :hg:`add`.
2417 2417
2418 2418 .. container:: verbose
2419 2419
2420 2420 Examples:
2421 2421
2422 2422 - forget newly-added binary files::
2423 2423
2424 2424 hg forget "set:added() and binary()"
2425 2425
2426 2426 - forget files that would be excluded by .hgignore::
2427 2427
2428 2428 hg forget "set:hgignore()"
2429 2429
2430 2430 Returns 0 on success.
2431 2431 """
2432 2432
2433 2433 if not pats:
2434 2434 raise util.Abort(_('no files specified'))
2435 2435
2436 2436 wctx = repo[None]
2437 2437 m = scmutil.match(wctx, pats, opts)
2438 2438 s = repo.status(match=m, clean=True)
2439 2439 forget = sorted(s[0] + s[1] + s[3] + s[6])
2440 2440 subforget = {}
2441 2441 errs = 0
2442 2442
2443 2443 for subpath in wctx.substate:
2444 2444 sub = wctx.sub(subpath)
2445 2445 try:
2446 2446 submatch = matchmod.narrowmatcher(subpath, m)
2447 2447 for fsub in sub.walk(submatch):
2448 2448 if submatch.exact(fsub):
2449 2449 subforget[subpath + '/' + fsub] = (fsub, sub)
2450 2450 except error.LookupError:
2451 2451 ui.status(_("skipping missing subrepository: %s\n") % subpath)
2452 2452
2453 2453 for f in m.files():
2454 2454 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2455 2455 if f not in subforget:
2456 2456 if os.path.exists(m.rel(f)):
2457 2457 ui.warn(_('not removing %s: file is already untracked\n')
2458 2458 % m.rel(f))
2459 2459 errs = 1
2460 2460
2461 2461 for f in forget:
2462 2462 if ui.verbose or not m.exact(f):
2463 2463 ui.status(_('removing %s\n') % m.rel(f))
2464 2464
2465 2465 if ui.verbose:
2466 2466 for f in sorted(subforget.keys()):
2467 2467 ui.status(_('removing %s\n') % m.rel(f))
2468 2468
2469 2469 wctx.forget(forget)
2470 2470
2471 2471 for f in sorted(subforget.keys()):
2472 2472 fsub, sub = subforget[f]
2473 2473 sub.forget([fsub])
2474 2474
2475 2475 return errs
2476 2476
2477 2477 @command(
2478 2478 'graft',
2479 2479 [('c', 'continue', False, _('resume interrupted graft')),
2480 2480 ('e', 'edit', False, _('invoke editor on commit messages')),
2481 2481 ('D', 'currentdate', False,
2482 2482 _('record the current date as commit date')),
2483 2483 ('U', 'currentuser', False,
2484 2484 _('record the current user as committer'), _('DATE'))]
2485 2485 + commitopts2 + mergetoolopts,
2486 2486 _('[OPTION]... REVISION...'))
2487 2487 def graft(ui, repo, *revs, **opts):
2488 2488 '''copy changes from other branches onto the current branch
2489 2489
2490 2490 This command uses Mercurial's merge logic to copy individual
2491 2491 changes from other branches without merging branches in the
2492 2492 history graph. This is sometimes known as 'backporting' or
2493 2493 'cherry-picking'. By default, graft will copy user, date, and
2494 2494 description from the source changesets.
2495 2495
2496 2496 Changesets that are ancestors of the current revision, that have
2497 2497 already been grafted, or that are merges will be skipped.
2498 2498
2499 2499 If a graft merge results in conflicts, the graft process is
2500 2500 aborted so that the current merge can be manually resolved. Once
2501 2501 all conflicts are addressed, the graft process can be continued
2502 2502 with the -c/--continue option.
2503 2503
2504 2504 .. note::
2505 2505 The -c/--continue option does not reapply earlier options.
2506 2506
2507 2507 .. container:: verbose
2508 2508
2509 2509 Examples:
2510 2510
2511 2511 - copy a single change to the stable branch and edit its description::
2512 2512
2513 2513 hg update stable
2514 2514 hg graft --edit 9393
2515 2515
2516 2516 - graft a range of changesets with one exception, updating dates::
2517 2517
2518 2518 hg graft -D "2085::2093 and not 2091"
2519 2519
2520 2520 - continue a graft after resolving conflicts::
2521 2521
2522 2522 hg graft -c
2523 2523
2524 2524 - show the source of a grafted changeset::
2525 2525
2526 2526 hg log --debug -r tip
2527 2527
2528 2528 Returns 0 on successful completion.
2529 2529 '''
2530 2530
2531 2531 if not opts.get('user') and opts.get('currentuser'):
2532 2532 opts['user'] = ui.username()
2533 2533 if not opts.get('date') and opts.get('currentdate'):
2534 2534 opts['date'] = "%d %d" % util.makedate()
2535 2535
2536 2536 editor = None
2537 2537 if opts.get('edit'):
2538 2538 editor = cmdutil.commitforceeditor
2539 2539
2540 2540 cont = False
2541 2541 if opts['continue']:
2542 2542 cont = True
2543 2543 if revs:
2544 2544 raise util.Abort(_("can't specify --continue and revisions"))
2545 2545 # read in unfinished revisions
2546 2546 try:
2547 2547 nodes = repo.opener.read('graftstate').splitlines()
2548 2548 revs = [repo[node].rev() for node in nodes]
2549 2549 except IOError, inst:
2550 2550 if inst.errno != errno.ENOENT:
2551 2551 raise
2552 2552 raise util.Abort(_("no graft state found, can't continue"))
2553 2553 else:
2554 2554 cmdutil.bailifchanged(repo)
2555 2555 if not revs:
2556 2556 raise util.Abort(_('no revisions specified'))
2557 2557 revs = scmutil.revrange(repo, revs)
2558 2558
2559 2559 # check for merges
2560 2560 for rev in repo.revs('%ld and merge()', revs):
2561 2561 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2562 2562 revs.remove(rev)
2563 2563 if not revs:
2564 2564 return -1
2565 2565
2566 2566 # check for ancestors of dest branch
2567 2567 for rev in repo.revs('::. and %ld', revs):
2568 2568 ui.warn(_('skipping ancestor revision %s\n') % rev)
2569 2569 revs.remove(rev)
2570 2570 if not revs:
2571 2571 return -1
2572 2572
2573 # analyze revs for earlier grafts
2574 ids = {}
2575 for ctx in repo.set("%ld", revs):
2576 ids[ctx.hex()] = ctx.rev()
2577 n = ctx.extra().get('source')
2578 if n:
2579 ids[n] = ctx.rev()
2580
2573 2581 # check ancestors for earlier grafts
2574 2582 ui.debug('scanning for duplicate grafts\n')
2575 2583 for ctx in repo.set("::. - ::%ld", revs):
2576 2584 n = ctx.extra().get('source')
2577 if n and n in repo:
2585 if n in ids:
2578 2586 r = repo[n].rev()
2579 2587 if r in revs:
2580 2588 ui.warn(_('skipping already grafted revision %s\n') % r)
2581 2589 revs.remove(r)
2590 elif ids[n] in revs:
2591 ui.warn(_('skipping already grafted revision %s '
2592 '(same origin %d)\n') % (ids[n], r))
2593 revs.remove(ids[n])
2594 elif ctx.hex() in ids:
2595 r = ids[ctx.hex()]
2596 ui.warn(_('skipping already grafted revision %s '
2597 '(was grafted from %d)\n') % (r, ctx.rev()))
2598 revs.remove(r)
2582 2599 if not revs:
2583 2600 return -1
2584 2601
2585 2602 for pos, ctx in enumerate(repo.set("%ld", revs)):
2586 2603 current = repo['.']
2587 2604 ui.status(_('grafting revision %s\n') % ctx.rev())
2588 2605
2589 2606 # we don't merge the first commit when continuing
2590 2607 if not cont:
2591 2608 # perform the graft merge with p1(rev) as 'ancestor'
2592 2609 try:
2593 2610 # ui.forcemerge is an internal variable, do not document
2594 2611 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2595 2612 stats = mergemod.update(repo, ctx.node(), True, True, False,
2596 2613 ctx.p1().node())
2597 2614 finally:
2598 2615 ui.setconfig('ui', 'forcemerge', '')
2599 2616 # drop the second merge parent
2600 2617 repo.dirstate.setparents(current.node(), nullid)
2601 2618 repo.dirstate.write()
2602 2619 # fix up dirstate for copies and renames
2603 2620 cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid)
2604 2621 # report any conflicts
2605 2622 if stats and stats[3] > 0:
2606 2623 # write out state for --continue
2607 2624 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2608 2625 repo.opener.write('graftstate', ''.join(nodelines))
2609 2626 raise util.Abort(
2610 2627 _("unresolved conflicts, can't continue"),
2611 2628 hint=_('use hg resolve and hg graft --continue'))
2612 2629 else:
2613 2630 cont = False
2614 2631
2615 2632 # commit
2616 extra = {'source': ctx.hex()}
2633 source = ctx.extra().get('source')
2634 if not source:
2635 source = ctx.hex()
2636 extra = {'source': source}
2617 2637 user = ctx.user()
2618 2638 if opts.get('user'):
2619 2639 user = opts['user']
2620 2640 date = ctx.date()
2621 2641 if opts.get('date'):
2622 2642 date = opts['date']
2623 2643 repo.commit(text=ctx.description(), user=user,
2624 2644 date=date, extra=extra, editor=editor)
2625 2645
2626 2646 # remove state when we complete successfully
2627 2647 if os.path.exists(repo.join('graftstate')):
2628 2648 util.unlinkpath(repo.join('graftstate'))
2629 2649
2630 2650 return 0
2631 2651
2632 2652 @command('grep',
2633 2653 [('0', 'print0', None, _('end fields with NUL')),
2634 2654 ('', 'all', None, _('print all revisions that match')),
2635 2655 ('a', 'text', None, _('treat all files as text')),
2636 2656 ('f', 'follow', None,
2637 2657 _('follow changeset history,'
2638 2658 ' or file history across copies and renames')),
2639 2659 ('i', 'ignore-case', None, _('ignore case when matching')),
2640 2660 ('l', 'files-with-matches', None,
2641 2661 _('print only filenames and revisions that match')),
2642 2662 ('n', 'line-number', None, _('print matching line numbers')),
2643 2663 ('r', 'rev', [],
2644 2664 _('only search files changed within revision range'), _('REV')),
2645 2665 ('u', 'user', None, _('list the author (long with -v)')),
2646 2666 ('d', 'date', None, _('list the date (short with -q)')),
2647 2667 ] + walkopts,
2648 2668 _('[OPTION]... PATTERN [FILE]...'))
2649 2669 def grep(ui, repo, pattern, *pats, **opts):
2650 2670 """search for a pattern in specified files and revisions
2651 2671
2652 2672 Search revisions of files for a regular expression.
2653 2673
2654 2674 This command behaves differently than Unix grep. It only accepts
2655 2675 Python/Perl regexps. It searches repository history, not the
2656 2676 working directory. It always prints the revision number in which a
2657 2677 match appears.
2658 2678
2659 2679 By default, grep only prints output for the first revision of a
2660 2680 file in which it finds a match. To get it to print every revision
2661 2681 that contains a change in match status ("-" for a match that
2662 2682 becomes a non-match, or "+" for a non-match that becomes a match),
2663 2683 use the --all flag.
2664 2684
2665 2685 Returns 0 if a match is found, 1 otherwise.
2666 2686 """
2667 2687 reflags = 0
2668 2688 if opts.get('ignore_case'):
2669 2689 reflags |= re.I
2670 2690 try:
2671 2691 regexp = re.compile(pattern, reflags)
2672 2692 except re.error, inst:
2673 2693 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2674 2694 return 1
2675 2695 sep, eol = ':', '\n'
2676 2696 if opts.get('print0'):
2677 2697 sep = eol = '\0'
2678 2698
2679 2699 getfile = util.lrucachefunc(repo.file)
2680 2700
2681 2701 def matchlines(body):
2682 2702 begin = 0
2683 2703 linenum = 0
2684 2704 while True:
2685 2705 match = regexp.search(body, begin)
2686 2706 if not match:
2687 2707 break
2688 2708 mstart, mend = match.span()
2689 2709 linenum += body.count('\n', begin, mstart) + 1
2690 2710 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2691 2711 begin = body.find('\n', mend) + 1 or len(body) + 1
2692 2712 lend = begin - 1
2693 2713 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2694 2714
2695 2715 class linestate(object):
2696 2716 def __init__(self, line, linenum, colstart, colend):
2697 2717 self.line = line
2698 2718 self.linenum = linenum
2699 2719 self.colstart = colstart
2700 2720 self.colend = colend
2701 2721
2702 2722 def __hash__(self):
2703 2723 return hash((self.linenum, self.line))
2704 2724
2705 2725 def __eq__(self, other):
2706 2726 return self.line == other.line
2707 2727
2708 2728 matches = {}
2709 2729 copies = {}
2710 2730 def grepbody(fn, rev, body):
2711 2731 matches[rev].setdefault(fn, [])
2712 2732 m = matches[rev][fn]
2713 2733 for lnum, cstart, cend, line in matchlines(body):
2714 2734 s = linestate(line, lnum, cstart, cend)
2715 2735 m.append(s)
2716 2736
2717 2737 def difflinestates(a, b):
2718 2738 sm = difflib.SequenceMatcher(None, a, b)
2719 2739 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2720 2740 if tag == 'insert':
2721 2741 for i in xrange(blo, bhi):
2722 2742 yield ('+', b[i])
2723 2743 elif tag == 'delete':
2724 2744 for i in xrange(alo, ahi):
2725 2745 yield ('-', a[i])
2726 2746 elif tag == 'replace':
2727 2747 for i in xrange(alo, ahi):
2728 2748 yield ('-', a[i])
2729 2749 for i in xrange(blo, bhi):
2730 2750 yield ('+', b[i])
2731 2751
2732 2752 def display(fn, ctx, pstates, states):
2733 2753 rev = ctx.rev()
2734 2754 datefunc = ui.quiet and util.shortdate or util.datestr
2735 2755 found = False
2736 2756 filerevmatches = {}
2737 2757 def binary():
2738 2758 flog = getfile(fn)
2739 2759 return util.binary(flog.read(ctx.filenode(fn)))
2740 2760
2741 2761 if opts.get('all'):
2742 2762 iter = difflinestates(pstates, states)
2743 2763 else:
2744 2764 iter = [('', l) for l in states]
2745 2765 for change, l in iter:
2746 2766 cols = [fn, str(rev)]
2747 2767 before, match, after = None, None, None
2748 2768 if opts.get('line_number'):
2749 2769 cols.append(str(l.linenum))
2750 2770 if opts.get('all'):
2751 2771 cols.append(change)
2752 2772 if opts.get('user'):
2753 2773 cols.append(ui.shortuser(ctx.user()))
2754 2774 if opts.get('date'):
2755 2775 cols.append(datefunc(ctx.date()))
2756 2776 if opts.get('files_with_matches'):
2757 2777 c = (fn, rev)
2758 2778 if c in filerevmatches:
2759 2779 continue
2760 2780 filerevmatches[c] = 1
2761 2781 else:
2762 2782 before = l.line[:l.colstart]
2763 2783 match = l.line[l.colstart:l.colend]
2764 2784 after = l.line[l.colend:]
2765 2785 ui.write(sep.join(cols))
2766 2786 if before is not None:
2767 2787 if not opts.get('text') and binary():
2768 2788 ui.write(sep + " Binary file matches")
2769 2789 else:
2770 2790 ui.write(sep + before)
2771 2791 ui.write(match, label='grep.match')
2772 2792 ui.write(after)
2773 2793 ui.write(eol)
2774 2794 found = True
2775 2795 return found
2776 2796
2777 2797 skip = {}
2778 2798 revfiles = {}
2779 2799 matchfn = scmutil.match(repo[None], pats, opts)
2780 2800 found = False
2781 2801 follow = opts.get('follow')
2782 2802
2783 2803 def prep(ctx, fns):
2784 2804 rev = ctx.rev()
2785 2805 pctx = ctx.p1()
2786 2806 parent = pctx.rev()
2787 2807 matches.setdefault(rev, {})
2788 2808 matches.setdefault(parent, {})
2789 2809 files = revfiles.setdefault(rev, [])
2790 2810 for fn in fns:
2791 2811 flog = getfile(fn)
2792 2812 try:
2793 2813 fnode = ctx.filenode(fn)
2794 2814 except error.LookupError:
2795 2815 continue
2796 2816
2797 2817 copied = flog.renamed(fnode)
2798 2818 copy = follow and copied and copied[0]
2799 2819 if copy:
2800 2820 copies.setdefault(rev, {})[fn] = copy
2801 2821 if fn in skip:
2802 2822 if copy:
2803 2823 skip[copy] = True
2804 2824 continue
2805 2825 files.append(fn)
2806 2826
2807 2827 if fn not in matches[rev]:
2808 2828 grepbody(fn, rev, flog.read(fnode))
2809 2829
2810 2830 pfn = copy or fn
2811 2831 if pfn not in matches[parent]:
2812 2832 try:
2813 2833 fnode = pctx.filenode(pfn)
2814 2834 grepbody(pfn, parent, flog.read(fnode))
2815 2835 except error.LookupError:
2816 2836 pass
2817 2837
2818 2838 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2819 2839 rev = ctx.rev()
2820 2840 parent = ctx.p1().rev()
2821 2841 for fn in sorted(revfiles.get(rev, [])):
2822 2842 states = matches[rev][fn]
2823 2843 copy = copies.get(rev, {}).get(fn)
2824 2844 if fn in skip:
2825 2845 if copy:
2826 2846 skip[copy] = True
2827 2847 continue
2828 2848 pstates = matches.get(parent, {}).get(copy or fn, [])
2829 2849 if pstates or states:
2830 2850 r = display(fn, ctx, pstates, states)
2831 2851 found = found or r
2832 2852 if r and not opts.get('all'):
2833 2853 skip[fn] = True
2834 2854 if copy:
2835 2855 skip[copy] = True
2836 2856 del matches[rev]
2837 2857 del revfiles[rev]
2838 2858
2839 2859 return not found
2840 2860
2841 2861 @command('heads',
2842 2862 [('r', 'rev', '',
2843 2863 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2844 2864 ('t', 'topo', False, _('show topological heads only')),
2845 2865 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2846 2866 ('c', 'closed', False, _('show normal and closed branch heads')),
2847 2867 ] + templateopts,
2848 2868 _('[-ac] [-r STARTREV] [REV]...'))
2849 2869 def heads(ui, repo, *branchrevs, **opts):
2850 2870 """show current repository heads or show branch heads
2851 2871
2852 2872 With no arguments, show all repository branch heads.
2853 2873
2854 2874 Repository "heads" are changesets with no child changesets. They are
2855 2875 where development generally takes place and are the usual targets
2856 2876 for update and merge operations. Branch heads are changesets that have
2857 2877 no child changeset on the same branch.
2858 2878
2859 2879 If one or more REVs are given, only branch heads on the branches
2860 2880 associated with the specified changesets are shown. This means
2861 2881 that you can use :hg:`heads foo` to see the heads on a branch
2862 2882 named ``foo``.
2863 2883
2864 2884 If -c/--closed is specified, also show branch heads marked closed
2865 2885 (see :hg:`commit --close-branch`).
2866 2886
2867 2887 If STARTREV is specified, only those heads that are descendants of
2868 2888 STARTREV will be displayed.
2869 2889
2870 2890 If -t/--topo is specified, named branch mechanics will be ignored and only
2871 2891 changesets without children will be shown.
2872 2892
2873 2893 Returns 0 if matching heads are found, 1 if not.
2874 2894 """
2875 2895
2876 2896 start = None
2877 2897 if 'rev' in opts:
2878 2898 start = scmutil.revsingle(repo, opts['rev'], None).node()
2879 2899
2880 2900 if opts.get('topo'):
2881 2901 heads = [repo[h] for h in repo.heads(start)]
2882 2902 else:
2883 2903 heads = []
2884 2904 for branch in repo.branchmap():
2885 2905 heads += repo.branchheads(branch, start, opts.get('closed'))
2886 2906 heads = [repo[h] for h in heads]
2887 2907
2888 2908 if branchrevs:
2889 2909 branches = set(repo[br].branch() for br in branchrevs)
2890 2910 heads = [h for h in heads if h.branch() in branches]
2891 2911
2892 2912 if opts.get('active') and branchrevs:
2893 2913 dagheads = repo.heads(start)
2894 2914 heads = [h for h in heads if h.node() in dagheads]
2895 2915
2896 2916 if branchrevs:
2897 2917 haveheads = set(h.branch() for h in heads)
2898 2918 if branches - haveheads:
2899 2919 headless = ', '.join(b for b in branches - haveheads)
2900 2920 msg = _('no open branch heads found on branches %s')
2901 2921 if opts.get('rev'):
2902 2922 msg += _(' (started at %s)' % opts['rev'])
2903 2923 ui.warn((msg + '\n') % headless)
2904 2924
2905 2925 if not heads:
2906 2926 return 1
2907 2927
2908 2928 heads = sorted(heads, key=lambda x: -x.rev())
2909 2929 displayer = cmdutil.show_changeset(ui, repo, opts)
2910 2930 for ctx in heads:
2911 2931 displayer.show(ctx)
2912 2932 displayer.close()
2913 2933
2914 2934 @command('help',
2915 2935 [('e', 'extension', None, _('show only help for extensions')),
2916 2936 ('c', 'command', None, _('show only help for commands'))],
2917 2937 _('[-ec] [TOPIC]'))
2918 2938 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
2919 2939 """show help for a given topic or a help overview
2920 2940
2921 2941 With no arguments, print a list of commands with short help messages.
2922 2942
2923 2943 Given a topic, extension, or command name, print help for that
2924 2944 topic.
2925 2945
2926 2946 Returns 0 if successful.
2927 2947 """
2928 2948
2929 2949 textwidth = min(ui.termwidth(), 80) - 2
2930 2950
2931 2951 def optrst(options):
2932 2952 data = []
2933 2953 multioccur = False
2934 2954 for option in options:
2935 2955 if len(option) == 5:
2936 2956 shortopt, longopt, default, desc, optlabel = option
2937 2957 else:
2938 2958 shortopt, longopt, default, desc = option
2939 2959 optlabel = _("VALUE") # default label
2940 2960
2941 2961 if _("DEPRECATED") in desc and not ui.verbose:
2942 2962 continue
2943 2963
2944 2964 so = ''
2945 2965 if shortopt:
2946 2966 so = '-' + shortopt
2947 2967 lo = '--' + longopt
2948 2968 if default:
2949 2969 desc += _(" (default: %s)") % default
2950 2970
2951 2971 if isinstance(default, list):
2952 2972 lo += " %s [+]" % optlabel
2953 2973 multioccur = True
2954 2974 elif (default is not None) and not isinstance(default, bool):
2955 2975 lo += " %s" % optlabel
2956 2976
2957 2977 data.append((so, lo, desc))
2958 2978
2959 2979 rst = minirst.maketable(data, 1)
2960 2980
2961 2981 if multioccur:
2962 2982 rst += _("\n[+] marked option can be specified multiple times\n")
2963 2983
2964 2984 return rst
2965 2985
2966 2986 # list all option lists
2967 2987 def opttext(optlist, width):
2968 2988 rst = ''
2969 2989 if not optlist:
2970 2990 return ''
2971 2991
2972 2992 for title, options in optlist:
2973 2993 rst += '\n%s\n' % title
2974 2994 if options:
2975 2995 rst += "\n"
2976 2996 rst += optrst(options)
2977 2997 rst += '\n'
2978 2998
2979 2999 return '\n' + minirst.format(rst, width)
2980 3000
2981 3001 def addglobalopts(optlist, aliases):
2982 3002 if ui.quiet:
2983 3003 return []
2984 3004
2985 3005 if ui.verbose:
2986 3006 optlist.append((_("global options:"), globalopts))
2987 3007 if name == 'shortlist':
2988 3008 optlist.append((_('use "hg help" for the full list '
2989 3009 'of commands'), ()))
2990 3010 else:
2991 3011 if name == 'shortlist':
2992 3012 msg = _('use "hg help" for the full list of commands '
2993 3013 'or "hg -v" for details')
2994 3014 elif name and not full:
2995 3015 msg = _('use "hg help %s" to show the full help text' % name)
2996 3016 elif aliases:
2997 3017 msg = _('use "hg -v help%s" to show builtin aliases and '
2998 3018 'global options') % (name and " " + name or "")
2999 3019 else:
3000 3020 msg = _('use "hg -v help %s" to show more info') % name
3001 3021 optlist.append((msg, ()))
3002 3022
3003 3023 def helpcmd(name):
3004 3024 try:
3005 3025 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3006 3026 except error.AmbiguousCommand, inst:
3007 3027 # py3k fix: except vars can't be used outside the scope of the
3008 3028 # except block, nor can be used inside a lambda. python issue4617
3009 3029 prefix = inst.args[0]
3010 3030 select = lambda c: c.lstrip('^').startswith(prefix)
3011 3031 helplist(select)
3012 3032 return
3013 3033
3014 3034 # check if it's an invalid alias and display its error if it is
3015 3035 if getattr(entry[0], 'badalias', False):
3016 3036 if not unknowncmd:
3017 3037 entry[0](ui)
3018 3038 return
3019 3039
3020 3040 rst = ""
3021 3041
3022 3042 # synopsis
3023 3043 if len(entry) > 2:
3024 3044 if entry[2].startswith('hg'):
3025 3045 rst += "%s\n" % entry[2]
3026 3046 else:
3027 3047 rst += 'hg %s %s\n' % (aliases[0], entry[2])
3028 3048 else:
3029 3049 rst += 'hg %s\n' % aliases[0]
3030 3050
3031 3051 # aliases
3032 3052 if full and not ui.quiet and len(aliases) > 1:
3033 3053 rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
3034 3054
3035 3055 # description
3036 3056 doc = gettext(entry[0].__doc__)
3037 3057 if not doc:
3038 3058 doc = _("(no help text available)")
3039 3059 if util.safehasattr(entry[0], 'definition'): # aliased command
3040 3060 if entry[0].definition.startswith('!'): # shell alias
3041 3061 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3042 3062 else:
3043 3063 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3044 3064 if ui.quiet or not full:
3045 3065 doc = doc.splitlines()[0]
3046 3066 rst += "\n" + doc + "\n"
3047 3067
3048 3068 # check if this command shadows a non-trivial (multi-line)
3049 3069 # extension help text
3050 3070 try:
3051 3071 mod = extensions.find(name)
3052 3072 doc = gettext(mod.__doc__) or ''
3053 3073 if '\n' in doc.strip():
3054 3074 msg = _('use "hg help -e %s" to show help for '
3055 3075 'the %s extension') % (name, name)
3056 3076 rst += '\n%s\n' % msg
3057 3077 except KeyError:
3058 3078 pass
3059 3079
3060 3080 # options
3061 3081 if not ui.quiet and entry[1]:
3062 3082 rst += '\noptions:\n\n'
3063 3083 rst += optrst(entry[1])
3064 3084
3065 3085 if ui.verbose:
3066 3086 rst += '\nglobal options:\n\n'
3067 3087 rst += optrst(globalopts)
3068 3088
3069 3089 keep = ui.verbose and ['verbose'] or []
3070 3090 formatted, pruned = minirst.format(rst, textwidth, keep=keep)
3071 3091 ui.write(formatted)
3072 3092
3073 3093 if not ui.verbose:
3074 3094 if not full:
3075 3095 ui.write(_('\nuse "hg help %s" to show the full help text\n')
3076 3096 % name)
3077 3097 elif not ui.quiet:
3078 3098 ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
3079 3099
3080 3100
3081 3101 def helplist(select=None):
3082 3102 # list of commands
3083 3103 if name == "shortlist":
3084 3104 header = _('basic commands:\n\n')
3085 3105 else:
3086 3106 header = _('list of commands:\n\n')
3087 3107
3088 3108 h = {}
3089 3109 cmds = {}
3090 3110 for c, e in table.iteritems():
3091 3111 f = c.split("|", 1)[0]
3092 3112 if select and not select(f):
3093 3113 continue
3094 3114 if (not select and name != 'shortlist' and
3095 3115 e[0].__module__ != __name__):
3096 3116 continue
3097 3117 if name == "shortlist" and not f.startswith("^"):
3098 3118 continue
3099 3119 f = f.lstrip("^")
3100 3120 if not ui.debugflag and f.startswith("debug"):
3101 3121 continue
3102 3122 doc = e[0].__doc__
3103 3123 if doc and 'DEPRECATED' in doc and not ui.verbose:
3104 3124 continue
3105 3125 doc = gettext(doc)
3106 3126 if not doc:
3107 3127 doc = _("(no help text available)")
3108 3128 h[f] = doc.splitlines()[0].rstrip()
3109 3129 cmds[f] = c.lstrip("^")
3110 3130
3111 3131 if not h:
3112 3132 ui.status(_('no commands defined\n'))
3113 3133 return
3114 3134
3115 3135 ui.status(header)
3116 3136 fns = sorted(h)
3117 3137 m = max(map(len, fns))
3118 3138 for f in fns:
3119 3139 if ui.verbose:
3120 3140 commands = cmds[f].replace("|",", ")
3121 3141 ui.write(" %s:\n %s\n"%(commands, h[f]))
3122 3142 else:
3123 3143 ui.write('%s\n' % (util.wrap(h[f], textwidth,
3124 3144 initindent=' %-*s ' % (m, f),
3125 3145 hangindent=' ' * (m + 4))))
3126 3146
3127 3147 if not name:
3128 3148 text = help.listexts(_('enabled extensions:'), extensions.enabled())
3129 3149 if text:
3130 3150 ui.write("\n%s" % minirst.format(text, textwidth))
3131 3151
3132 3152 ui.write(_("\nadditional help topics:\n\n"))
3133 3153 topics = []
3134 3154 for names, header, doc in help.helptable:
3135 3155 topics.append((sorted(names, key=len, reverse=True)[0], header))
3136 3156 topics_len = max([len(s[0]) for s in topics])
3137 3157 for t, desc in topics:
3138 3158 ui.write(" %-*s %s\n" % (topics_len, t, desc))
3139 3159
3140 3160 optlist = []
3141 3161 addglobalopts(optlist, True)
3142 3162 ui.write(opttext(optlist, textwidth))
3143 3163
3144 3164 def helptopic(name):
3145 3165 for names, header, doc in help.helptable:
3146 3166 if name in names:
3147 3167 break
3148 3168 else:
3149 3169 raise error.UnknownCommand(name)
3150 3170
3151 3171 # description
3152 3172 if not doc:
3153 3173 doc = _("(no help text available)")
3154 3174 if util.safehasattr(doc, '__call__'):
3155 3175 doc = doc()
3156 3176
3157 3177 ui.write("%s\n\n" % header)
3158 3178 ui.write("%s" % minirst.format(doc, textwidth, indent=4))
3159 3179 try:
3160 3180 cmdutil.findcmd(name, table)
3161 3181 ui.write(_('\nuse "hg help -c %s" to see help for '
3162 3182 'the %s command\n') % (name, name))
3163 3183 except error.UnknownCommand:
3164 3184 pass
3165 3185
3166 3186 def helpext(name):
3167 3187 try:
3168 3188 mod = extensions.find(name)
3169 3189 doc = gettext(mod.__doc__) or _('no help text available')
3170 3190 except KeyError:
3171 3191 mod = None
3172 3192 doc = extensions.disabledext(name)
3173 3193 if not doc:
3174 3194 raise error.UnknownCommand(name)
3175 3195
3176 3196 if '\n' not in doc:
3177 3197 head, tail = doc, ""
3178 3198 else:
3179 3199 head, tail = doc.split('\n', 1)
3180 3200 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
3181 3201 if tail:
3182 3202 ui.write(minirst.format(tail, textwidth))
3183 3203 ui.status('\n')
3184 3204
3185 3205 if mod:
3186 3206 try:
3187 3207 ct = mod.cmdtable
3188 3208 except AttributeError:
3189 3209 ct = {}
3190 3210 modcmds = set([c.split('|', 1)[0] for c in ct])
3191 3211 helplist(modcmds.__contains__)
3192 3212 else:
3193 3213 ui.write(_('use "hg help extensions" for information on enabling '
3194 3214 'extensions\n'))
3195 3215
3196 3216 def helpextcmd(name):
3197 3217 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
3198 3218 doc = gettext(mod.__doc__).splitlines()[0]
3199 3219
3200 3220 msg = help.listexts(_("'%s' is provided by the following "
3201 3221 "extension:") % cmd, {ext: doc}, indent=4)
3202 3222 ui.write(minirst.format(msg, textwidth))
3203 3223 ui.write('\n')
3204 3224 ui.write(_('use "hg help extensions" for information on enabling '
3205 3225 'extensions\n'))
3206 3226
3207 3227 if name and name != 'shortlist':
3208 3228 i = None
3209 3229 if unknowncmd:
3210 3230 queries = (helpextcmd,)
3211 3231 elif opts.get('extension'):
3212 3232 queries = (helpext,)
3213 3233 elif opts.get('command'):
3214 3234 queries = (helpcmd,)
3215 3235 else:
3216 3236 queries = (helptopic, helpcmd, helpext, helpextcmd)
3217 3237 for f in queries:
3218 3238 try:
3219 3239 f(name)
3220 3240 i = None
3221 3241 break
3222 3242 except error.UnknownCommand, inst:
3223 3243 i = inst
3224 3244 if i:
3225 3245 raise i
3226 3246 else:
3227 3247 # program name
3228 3248 ui.status(_("Mercurial Distributed SCM\n"))
3229 3249 ui.status('\n')
3230 3250 helplist()
3231 3251
3232 3252
3233 3253 @command('identify|id',
3234 3254 [('r', 'rev', '',
3235 3255 _('identify the specified revision'), _('REV')),
3236 3256 ('n', 'num', None, _('show local revision number')),
3237 3257 ('i', 'id', None, _('show global revision id')),
3238 3258 ('b', 'branch', None, _('show branch')),
3239 3259 ('t', 'tags', None, _('show tags')),
3240 3260 ('B', 'bookmarks', None, _('show bookmarks'))],
3241 3261 _('[-nibtB] [-r REV] [SOURCE]'))
3242 3262 def identify(ui, repo, source=None, rev=None,
3243 3263 num=None, id=None, branch=None, tags=None, bookmarks=None):
3244 3264 """identify the working copy or specified revision
3245 3265
3246 3266 Print a summary identifying the repository state at REV using one or
3247 3267 two parent hash identifiers, followed by a "+" if the working
3248 3268 directory has uncommitted changes, the branch name (if not default),
3249 3269 a list of tags, and a list of bookmarks.
3250 3270
3251 3271 When REV is not given, print a summary of the current state of the
3252 3272 repository.
3253 3273
3254 3274 Specifying a path to a repository root or Mercurial bundle will
3255 3275 cause lookup to operate on that repository/bundle.
3256 3276
3257 3277 .. container:: verbose
3258 3278
3259 3279 Examples:
3260 3280
3261 3281 - generate a build identifier for the working directory::
3262 3282
3263 3283 hg id --id > build-id.dat
3264 3284
3265 3285 - find the revision corresponding to a tag::
3266 3286
3267 3287 hg id -n -r 1.3
3268 3288
3269 3289 - check the most recent revision of a remote repository::
3270 3290
3271 3291 hg id -r tip http://selenic.com/hg/
3272 3292
3273 3293 Returns 0 if successful.
3274 3294 """
3275 3295
3276 3296 if not repo and not source:
3277 3297 raise util.Abort(_("there is no Mercurial repository here "
3278 3298 "(.hg not found)"))
3279 3299
3280 3300 hexfunc = ui.debugflag and hex or short
3281 3301 default = not (num or id or branch or tags or bookmarks)
3282 3302 output = []
3283 3303 revs = []
3284 3304
3285 3305 if source:
3286 3306 source, branches = hg.parseurl(ui.expandpath(source))
3287 3307 repo = hg.peer(ui, {}, source)
3288 3308 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3289 3309
3290 3310 if not repo.local():
3291 3311 if num or branch or tags:
3292 3312 raise util.Abort(
3293 3313 _("can't query remote revision number, branch, or tags"))
3294 3314 if not rev and revs:
3295 3315 rev = revs[0]
3296 3316 if not rev:
3297 3317 rev = "tip"
3298 3318
3299 3319 remoterev = repo.lookup(rev)
3300 3320 if default or id:
3301 3321 output = [hexfunc(remoterev)]
3302 3322
3303 3323 def getbms():
3304 3324 bms = []
3305 3325
3306 3326 if 'bookmarks' in repo.listkeys('namespaces'):
3307 3327 hexremoterev = hex(remoterev)
3308 3328 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3309 3329 if bmr == hexremoterev]
3310 3330
3311 3331 return bms
3312 3332
3313 3333 if bookmarks:
3314 3334 output.extend(getbms())
3315 3335 elif default and not ui.quiet:
3316 3336 # multiple bookmarks for a single parent separated by '/'
3317 3337 bm = '/'.join(getbms())
3318 3338 if bm:
3319 3339 output.append(bm)
3320 3340 else:
3321 3341 if not rev:
3322 3342 ctx = repo[None]
3323 3343 parents = ctx.parents()
3324 3344 changed = ""
3325 3345 if default or id or num:
3326 3346 changed = util.any(repo.status()) and "+" or ""
3327 3347 if default or id:
3328 3348 output = ["%s%s" %
3329 3349 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3330 3350 if num:
3331 3351 output.append("%s%s" %
3332 3352 ('+'.join([str(p.rev()) for p in parents]), changed))
3333 3353 else:
3334 3354 ctx = scmutil.revsingle(repo, rev)
3335 3355 if default or id:
3336 3356 output = [hexfunc(ctx.node())]
3337 3357 if num:
3338 3358 output.append(str(ctx.rev()))
3339 3359
3340 3360 if default and not ui.quiet:
3341 3361 b = ctx.branch()
3342 3362 if b != 'default':
3343 3363 output.append("(%s)" % b)
3344 3364
3345 3365 # multiple tags for a single parent separated by '/'
3346 3366 t = '/'.join(ctx.tags())
3347 3367 if t:
3348 3368 output.append(t)
3349 3369
3350 3370 # multiple bookmarks for a single parent separated by '/'
3351 3371 bm = '/'.join(ctx.bookmarks())
3352 3372 if bm:
3353 3373 output.append(bm)
3354 3374 else:
3355 3375 if branch:
3356 3376 output.append(ctx.branch())
3357 3377
3358 3378 if tags:
3359 3379 output.extend(ctx.tags())
3360 3380
3361 3381 if bookmarks:
3362 3382 output.extend(ctx.bookmarks())
3363 3383
3364 3384 ui.write("%s\n" % ' '.join(output))
3365 3385
3366 3386 @command('import|patch',
3367 3387 [('p', 'strip', 1,
3368 3388 _('directory strip option for patch. This has the same '
3369 3389 'meaning as the corresponding patch option'), _('NUM')),
3370 3390 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3371 3391 ('e', 'edit', False, _('invoke editor on commit messages')),
3372 3392 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3373 3393 ('', 'no-commit', None,
3374 3394 _("don't commit, just update the working directory")),
3375 3395 ('', 'bypass', None,
3376 3396 _("apply patch without touching the working directory")),
3377 3397 ('', 'exact', None,
3378 3398 _('apply patch to the nodes from which it was generated')),
3379 3399 ('', 'import-branch', None,
3380 3400 _('use any branch information in patch (implied by --exact)'))] +
3381 3401 commitopts + commitopts2 + similarityopts,
3382 3402 _('[OPTION]... PATCH...'))
3383 3403 def import_(ui, repo, patch1=None, *patches, **opts):
3384 3404 """import an ordered set of patches
3385 3405
3386 3406 Import a list of patches and commit them individually (unless
3387 3407 --no-commit is specified).
3388 3408
3389 3409 If there are outstanding changes in the working directory, import
3390 3410 will abort unless given the -f/--force flag.
3391 3411
3392 3412 You can import a patch straight from a mail message. Even patches
3393 3413 as attachments work (to use the body part, it must have type
3394 3414 text/plain or text/x-patch). From and Subject headers of email
3395 3415 message are used as default committer and commit message. All
3396 3416 text/plain body parts before first diff are added to commit
3397 3417 message.
3398 3418
3399 3419 If the imported patch was generated by :hg:`export`, user and
3400 3420 description from patch override values from message headers and
3401 3421 body. Values given on command line with -m/--message and -u/--user
3402 3422 override these.
3403 3423
3404 3424 If --exact is specified, import will set the working directory to
3405 3425 the parent of each patch before applying it, and will abort if the
3406 3426 resulting changeset has a different ID than the one recorded in
3407 3427 the patch. This may happen due to character set problems or other
3408 3428 deficiencies in the text patch format.
3409 3429
3410 3430 Use --bypass to apply and commit patches directly to the
3411 3431 repository, not touching the working directory. Without --exact,
3412 3432 patches will be applied on top of the working directory parent
3413 3433 revision.
3414 3434
3415 3435 With -s/--similarity, hg will attempt to discover renames and
3416 3436 copies in the patch in the same way as 'addremove'.
3417 3437
3418 3438 To read a patch from standard input, use "-" as the patch name. If
3419 3439 a URL is specified, the patch will be downloaded from it.
3420 3440 See :hg:`help dates` for a list of formats valid for -d/--date.
3421 3441
3422 3442 .. container:: verbose
3423 3443
3424 3444 Examples:
3425 3445
3426 3446 - import a traditional patch from a website and detect renames::
3427 3447
3428 3448 hg import -s 80 http://example.com/bugfix.patch
3429 3449
3430 3450 - import a changeset from an hgweb server::
3431 3451
3432 3452 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3433 3453
3434 3454 - import all the patches in an Unix-style mbox::
3435 3455
3436 3456 hg import incoming-patches.mbox
3437 3457
3438 3458 - attempt to exactly restore an exported changeset (not always
3439 3459 possible)::
3440 3460
3441 3461 hg import --exact proposed-fix.patch
3442 3462
3443 3463 Returns 0 on success.
3444 3464 """
3445 3465
3446 3466 if not patch1:
3447 3467 raise util.Abort(_('need at least one patch to import'))
3448 3468
3449 3469 patches = (patch1,) + patches
3450 3470
3451 3471 date = opts.get('date')
3452 3472 if date:
3453 3473 opts['date'] = util.parsedate(date)
3454 3474
3455 3475 editor = cmdutil.commiteditor
3456 3476 if opts.get('edit'):
3457 3477 editor = cmdutil.commitforceeditor
3458 3478
3459 3479 update = not opts.get('bypass')
3460 3480 if not update and opts.get('no_commit'):
3461 3481 raise util.Abort(_('cannot use --no-commit with --bypass'))
3462 3482 try:
3463 3483 sim = float(opts.get('similarity') or 0)
3464 3484 except ValueError:
3465 3485 raise util.Abort(_('similarity must be a number'))
3466 3486 if sim < 0 or sim > 100:
3467 3487 raise util.Abort(_('similarity must be between 0 and 100'))
3468 3488 if sim and not update:
3469 3489 raise util.Abort(_('cannot use --similarity with --bypass'))
3470 3490
3471 3491 if (opts.get('exact') or not opts.get('force')) and update:
3472 3492 cmdutil.bailifchanged(repo)
3473 3493
3474 3494 base = opts["base"]
3475 3495 strip = opts["strip"]
3476 3496 wlock = lock = tr = None
3477 3497 msgs = []
3478 3498
3479 3499 def checkexact(repo, n, nodeid):
3480 3500 if opts.get('exact') and hex(n) != nodeid:
3481 3501 repo.rollback()
3482 3502 raise util.Abort(_('patch is damaged or loses information'))
3483 3503
3484 3504 def tryone(ui, hunk, parents):
3485 3505 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3486 3506 patch.extract(ui, hunk)
3487 3507
3488 3508 if not tmpname:
3489 3509 return (None, None)
3490 3510 msg = _('applied to working directory')
3491 3511
3492 3512 try:
3493 3513 cmdline_message = cmdutil.logmessage(ui, opts)
3494 3514 if cmdline_message:
3495 3515 # pickup the cmdline msg
3496 3516 message = cmdline_message
3497 3517 elif message:
3498 3518 # pickup the patch msg
3499 3519 message = message.strip()
3500 3520 else:
3501 3521 # launch the editor
3502 3522 message = None
3503 3523 ui.debug('message:\n%s\n' % message)
3504 3524
3505 3525 if len(parents) == 1:
3506 3526 parents.append(repo[nullid])
3507 3527 if opts.get('exact'):
3508 3528 if not nodeid or not p1:
3509 3529 raise util.Abort(_('not a Mercurial patch'))
3510 3530 p1 = repo[p1]
3511 3531 p2 = repo[p2 or nullid]
3512 3532 elif p2:
3513 3533 try:
3514 3534 p1 = repo[p1]
3515 3535 p2 = repo[p2]
3536 # Without any options, consider p2 only if the
3537 # patch is being applied on top of the recorded
3538 # first parent.
3539 if p1 != parents[0]:
3540 p1 = parents[0]
3541 p2 = repo[nullid]
3516 3542 except error.RepoError:
3517 3543 p1, p2 = parents
3518 3544 else:
3519 3545 p1, p2 = parents
3520 3546
3521 3547 n = None
3522 3548 if update:
3523 if opts.get('exact') and p1 != parents[0]:
3549 if p1 != parents[0]:
3524 3550 hg.clean(repo, p1.node())
3525 if p1 != parents[0] and p2 != parents[1]:
3551 if p2 != parents[1]:
3526 3552 repo.dirstate.setparents(p1.node(), p2.node())
3527 3553
3528 3554 if opts.get('exact') or opts.get('import_branch'):
3529 3555 repo.dirstate.setbranch(branch or 'default')
3530 3556
3531 3557 files = set()
3532 3558 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3533 3559 eolmode=None, similarity=sim / 100.0)
3534 3560 files = list(files)
3535 3561 if opts.get('no_commit'):
3536 3562 if message:
3537 3563 msgs.append(message)
3538 3564 else:
3539 if opts.get('exact'):
3565 if opts.get('exact') or p2:
3566 # If you got here, you either use --force and know what
3567 # you are doing or used --exact or a merge patch while
3568 # being updated to its first parent.
3540 3569 m = None
3541 3570 else:
3542 3571 m = scmutil.matchfiles(repo, files or [])
3543 3572 n = repo.commit(message, opts.get('user') or user,
3544 3573 opts.get('date') or date, match=m,
3545 3574 editor=editor)
3546 3575 checkexact(repo, n, nodeid)
3547 3576 else:
3548 3577 if opts.get('exact') or opts.get('import_branch'):
3549 3578 branch = branch or 'default'
3550 3579 else:
3551 3580 branch = p1.branch()
3552 3581 store = patch.filestore()
3553 3582 try:
3554 3583 files = set()
3555 3584 try:
3556 3585 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3557 3586 files, eolmode=None)
3558 3587 except patch.PatchError, e:
3559 3588 raise util.Abort(str(e))
3560 3589 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3561 3590 message,
3562 3591 opts.get('user') or user,
3563 3592 opts.get('date') or date,
3564 3593 branch, files, store,
3565 3594 editor=cmdutil.commiteditor)
3566 3595 repo.savecommitmessage(memctx.description())
3567 3596 n = memctx.commit()
3568 3597 checkexact(repo, n, nodeid)
3569 3598 finally:
3570 3599 store.close()
3571 3600 if n:
3572 3601 # i18n: refers to a short changeset id
3573 3602 msg = _('created %s') % short(n)
3574 3603 return (msg, n)
3575 3604 finally:
3576 3605 os.unlink(tmpname)
3577 3606
3578 3607 try:
3579 3608 try:
3580 3609 wlock = repo.wlock()
3581 3610 lock = repo.lock()
3582 3611 tr = repo.transaction('import')
3583 3612 parents = repo.parents()
3584 3613 for patchurl in patches:
3585 3614 if patchurl == '-':
3586 3615 ui.status(_('applying patch from stdin\n'))
3587 3616 patchfile = ui.fin
3588 3617 patchurl = 'stdin' # for error message
3589 3618 else:
3590 3619 patchurl = os.path.join(base, patchurl)
3591 3620 ui.status(_('applying %s\n') % patchurl)
3592 3621 patchfile = url.open(ui, patchurl)
3593 3622
3594 3623 haspatch = False
3595 3624 for hunk in patch.split(patchfile):
3596 3625 (msg, node) = tryone(ui, hunk, parents)
3597 3626 if msg:
3598 3627 haspatch = True
3599 3628 ui.note(msg + '\n')
3600 3629 if update or opts.get('exact'):
3601 3630 parents = repo.parents()
3602 3631 else:
3603 3632 parents = [repo[node]]
3604 3633
3605 3634 if not haspatch:
3606 3635 raise util.Abort(_('%s: no diffs found') % patchurl)
3607 3636
3608 3637 tr.close()
3609 3638 if msgs:
3610 3639 repo.savecommitmessage('\n* * *\n'.join(msgs))
3611 3640 except:
3612 3641 # wlock.release() indirectly calls dirstate.write(): since
3613 3642 # we're crashing, we do not want to change the working dir
3614 3643 # parent after all, so make sure it writes nothing
3615 3644 repo.dirstate.invalidate()
3616 3645 raise
3617 3646 finally:
3618 3647 if tr:
3619 3648 tr.release()
3620 3649 release(lock, wlock)
3621 3650
3622 3651 @command('incoming|in',
3623 3652 [('f', 'force', None,
3624 3653 _('run even if remote repository is unrelated')),
3625 3654 ('n', 'newest-first', None, _('show newest record first')),
3626 3655 ('', 'bundle', '',
3627 3656 _('file to store the bundles into'), _('FILE')),
3628 3657 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3629 3658 ('B', 'bookmarks', False, _("compare bookmarks")),
3630 3659 ('b', 'branch', [],
3631 3660 _('a specific branch you would like to pull'), _('BRANCH')),
3632 3661 ] + logopts + remoteopts + subrepoopts,
3633 3662 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3634 3663 def incoming(ui, repo, source="default", **opts):
3635 3664 """show new changesets found in source
3636 3665
3637 3666 Show new changesets found in the specified path/URL or the default
3638 3667 pull location. These are the changesets that would have been pulled
3639 3668 if a pull at the time you issued this command.
3640 3669
3641 3670 For remote repository, using --bundle avoids downloading the
3642 3671 changesets twice if the incoming is followed by a pull.
3643 3672
3644 3673 See pull for valid source format details.
3645 3674
3646 3675 Returns 0 if there are incoming changes, 1 otherwise.
3647 3676 """
3648 3677 if opts.get('bundle') and opts.get('subrepos'):
3649 3678 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3650 3679
3651 3680 if opts.get('bookmarks'):
3652 3681 source, branches = hg.parseurl(ui.expandpath(source),
3653 3682 opts.get('branch'))
3654 3683 other = hg.peer(repo, opts, source)
3655 3684 if 'bookmarks' not in other.listkeys('namespaces'):
3656 3685 ui.warn(_("remote doesn't support bookmarks\n"))
3657 3686 return 0
3658 3687 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3659 3688 return bookmarks.diff(ui, repo, other)
3660 3689
3661 3690 repo._subtoppath = ui.expandpath(source)
3662 3691 try:
3663 3692 return hg.incoming(ui, repo, source, opts)
3664 3693 finally:
3665 3694 del repo._subtoppath
3666 3695
3667 3696
3668 3697 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3669 3698 def init(ui, dest=".", **opts):
3670 3699 """create a new repository in the given directory
3671 3700
3672 3701 Initialize a new repository in the given directory. If the given
3673 3702 directory does not exist, it will be created.
3674 3703
3675 3704 If no directory is given, the current directory is used.
3676 3705
3677 3706 It is possible to specify an ``ssh://`` URL as the destination.
3678 3707 See :hg:`help urls` for more information.
3679 3708
3680 3709 Returns 0 on success.
3681 3710 """
3682 3711 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3683 3712
3684 3713 @command('locate',
3685 3714 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3686 3715 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3687 3716 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3688 3717 ] + walkopts,
3689 3718 _('[OPTION]... [PATTERN]...'))
3690 3719 def locate(ui, repo, *pats, **opts):
3691 3720 """locate files matching specific patterns
3692 3721
3693 3722 Print files under Mercurial control in the working directory whose
3694 3723 names match the given patterns.
3695 3724
3696 3725 By default, this command searches all directories in the working
3697 3726 directory. To search just the current directory and its
3698 3727 subdirectories, use "--include .".
3699 3728
3700 3729 If no patterns are given to match, this command prints the names
3701 3730 of all files under Mercurial control in the working directory.
3702 3731
3703 3732 If you want to feed the output of this command into the "xargs"
3704 3733 command, use the -0 option to both this command and "xargs". This
3705 3734 will avoid the problem of "xargs" treating single filenames that
3706 3735 contain whitespace as multiple filenames.
3707 3736
3708 3737 Returns 0 if a match is found, 1 otherwise.
3709 3738 """
3710 3739 end = opts.get('print0') and '\0' or '\n'
3711 3740 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3712 3741
3713 3742 ret = 1
3714 3743 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3715 3744 m.bad = lambda x, y: False
3716 3745 for abs in repo[rev].walk(m):
3717 3746 if not rev and abs not in repo.dirstate:
3718 3747 continue
3719 3748 if opts.get('fullpath'):
3720 3749 ui.write(repo.wjoin(abs), end)
3721 3750 else:
3722 3751 ui.write(((pats and m.rel(abs)) or abs), end)
3723 3752 ret = 0
3724 3753
3725 3754 return ret
3726 3755
3727 3756 @command('^log|history',
3728 3757 [('f', 'follow', None,
3729 3758 _('follow changeset history, or file history across copies and renames')),
3730 3759 ('', 'follow-first', None,
3731 3760 _('only follow the first parent of merge changesets (DEPRECATED)')),
3732 3761 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3733 3762 ('C', 'copies', None, _('show copied files')),
3734 3763 ('k', 'keyword', [],
3735 3764 _('do case-insensitive search for a given text'), _('TEXT')),
3736 3765 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3737 3766 ('', 'removed', None, _('include revisions where files were removed')),
3738 3767 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3739 3768 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3740 3769 ('', 'only-branch', [],
3741 3770 _('show only changesets within the given named branch (DEPRECATED)'),
3742 3771 _('BRANCH')),
3743 3772 ('b', 'branch', [],
3744 3773 _('show changesets within the given named branch'), _('BRANCH')),
3745 3774 ('P', 'prune', [],
3746 3775 _('do not display revision or any of its ancestors'), _('REV')),
3747 3776 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3748 3777 ] + logopts + walkopts,
3749 3778 _('[OPTION]... [FILE]'))
3750 3779 def log(ui, repo, *pats, **opts):
3751 3780 """show revision history of entire repository or files
3752 3781
3753 3782 Print the revision history of the specified files or the entire
3754 3783 project.
3755 3784
3756 3785 If no revision range is specified, the default is ``tip:0`` unless
3757 3786 --follow is set, in which case the working directory parent is
3758 3787 used as the starting revision.
3759 3788
3760 3789 File history is shown without following rename or copy history of
3761 3790 files. Use -f/--follow with a filename to follow history across
3762 3791 renames and copies. --follow without a filename will only show
3763 3792 ancestors or descendants of the starting revision.
3764 3793
3765 3794 By default this command prints revision number and changeset id,
3766 3795 tags, non-trivial parents, user, date and time, and a summary for
3767 3796 each commit. When the -v/--verbose switch is used, the list of
3768 3797 changed files and full commit message are shown.
3769 3798
3770 3799 .. note::
3771 3800 log -p/--patch may generate unexpected diff output for merge
3772 3801 changesets, as it will only compare the merge changeset against
3773 3802 its first parent. Also, only files different from BOTH parents
3774 3803 will appear in files:.
3775 3804
3776 3805 .. note::
3777 3806 for performance reasons, log FILE may omit duplicate changes
3778 3807 made on branches and will not show deletions. To see all
3779 3808 changes including duplicates and deletions, use the --removed
3780 3809 switch.
3781 3810
3782 3811 .. container:: verbose
3783 3812
3784 3813 Some examples:
3785 3814
3786 3815 - changesets with full descriptions and file lists::
3787 3816
3788 3817 hg log -v
3789 3818
3790 3819 - changesets ancestral to the working directory::
3791 3820
3792 3821 hg log -f
3793 3822
3794 3823 - last 10 commits on the current branch::
3795 3824
3796 3825 hg log -l 10 -b .
3797 3826
3798 3827 - changesets showing all modifications of a file, including removals::
3799 3828
3800 3829 hg log --removed file.c
3801 3830
3802 3831 - all changesets that touch a directory, with diffs, excluding merges::
3803 3832
3804 3833 hg log -Mp lib/
3805 3834
3806 3835 - all revision numbers that match a keyword::
3807 3836
3808 3837 hg log -k bug --template "{rev}\\n"
3809 3838
3810 3839 - check if a given changeset is included is a tagged release::
3811 3840
3812 3841 hg log -r "a21ccf and ancestor(1.9)"
3813 3842
3814 3843 - find all changesets by some user in a date range::
3815 3844
3816 3845 hg log -k alice -d "may 2008 to jul 2008"
3817 3846
3818 3847 - summary of all changesets after the last tag::
3819 3848
3820 3849 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3821 3850
3822 3851 See :hg:`help dates` for a list of formats valid for -d/--date.
3823 3852
3824 3853 See :hg:`help revisions` and :hg:`help revsets` for more about
3825 3854 specifying revisions.
3826 3855
3827 3856 Returns 0 on success.
3828 3857 """
3829 3858
3830 3859 matchfn = scmutil.match(repo[None], pats, opts)
3831 3860 limit = cmdutil.loglimit(opts)
3832 3861 count = 0
3833 3862
3834 3863 endrev = None
3835 3864 if opts.get('copies') and opts.get('rev'):
3836 3865 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3837 3866
3838 3867 df = False
3839 3868 if opts["date"]:
3840 3869 df = util.matchdate(opts["date"])
3841 3870
3842 3871 branches = opts.get('branch', []) + opts.get('only_branch', [])
3843 3872 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3844 3873
3845 3874 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3846 3875 def prep(ctx, fns):
3847 3876 rev = ctx.rev()
3848 3877 parents = [p for p in repo.changelog.parentrevs(rev)
3849 3878 if p != nullrev]
3850 3879 if opts.get('no_merges') and len(parents) == 2:
3851 3880 return
3852 3881 if opts.get('only_merges') and len(parents) != 2:
3853 3882 return
3854 3883 if opts.get('branch') and ctx.branch() not in opts['branch']:
3855 3884 return
3856 3885 if not opts.get('hidden') and ctx.hidden():
3857 3886 return
3858 3887 if df and not df(ctx.date()[0]):
3859 3888 return
3860 3889 if opts['user'] and not [k for k in opts['user']
3861 3890 if k.lower() in ctx.user().lower()]:
3862 3891 return
3863 3892 if opts.get('keyword'):
3864 3893 for k in [kw.lower() for kw in opts['keyword']]:
3865 3894 if (k in ctx.user().lower() or
3866 3895 k in ctx.description().lower() or
3867 3896 k in " ".join(ctx.files()).lower()):
3868 3897 break
3869 3898 else:
3870 3899 return
3871 3900
3872 3901 copies = None
3873 3902 if opts.get('copies') and rev:
3874 3903 copies = []
3875 3904 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3876 3905 for fn in ctx.files():
3877 3906 rename = getrenamed(fn, rev)
3878 3907 if rename:
3879 3908 copies.append((fn, rename[0]))
3880 3909
3881 3910 revmatchfn = None
3882 3911 if opts.get('patch') or opts.get('stat'):
3883 3912 if opts.get('follow') or opts.get('follow_first'):
3884 3913 # note: this might be wrong when following through merges
3885 3914 revmatchfn = scmutil.match(repo[None], fns, default='path')
3886 3915 else:
3887 3916 revmatchfn = matchfn
3888 3917
3889 3918 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3890 3919
3891 3920 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3892 3921 if count == limit:
3893 3922 break
3894 3923 if displayer.flush(ctx.rev()):
3895 3924 count += 1
3896 3925 displayer.close()
3897 3926
3898 3927 @command('manifest',
3899 3928 [('r', 'rev', '', _('revision to display'), _('REV')),
3900 3929 ('', 'all', False, _("list files from all revisions"))],
3901 3930 _('[-r REV]'))
3902 3931 def manifest(ui, repo, node=None, rev=None, **opts):
3903 3932 """output the current or given revision of the project manifest
3904 3933
3905 3934 Print a list of version controlled files for the given revision.
3906 3935 If no revision is given, the first parent of the working directory
3907 3936 is used, or the null revision if no revision is checked out.
3908 3937
3909 3938 With -v, print file permissions, symlink and executable bits.
3910 3939 With --debug, print file revision hashes.
3911 3940
3912 3941 If option --all is specified, the list of all files from all revisions
3913 3942 is printed. This includes deleted and renamed files.
3914 3943
3915 3944 Returns 0 on success.
3916 3945 """
3917 3946 if opts.get('all'):
3918 3947 if rev or node:
3919 3948 raise util.Abort(_("can't specify a revision with --all"))
3920 3949
3921 3950 res = []
3922 3951 prefix = "data/"
3923 3952 suffix = ".i"
3924 3953 plen = len(prefix)
3925 3954 slen = len(suffix)
3926 3955 lock = repo.lock()
3927 3956 try:
3928 3957 for fn, b, size in repo.store.datafiles():
3929 3958 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3930 3959 res.append(fn[plen:-slen])
3931 3960 finally:
3932 3961 lock.release()
3933 3962 for f in sorted(res):
3934 3963 ui.write("%s\n" % f)
3935 3964 return
3936 3965
3937 3966 if rev and node:
3938 3967 raise util.Abort(_("please specify just one revision"))
3939 3968
3940 3969 if not node:
3941 3970 node = rev
3942 3971
3943 3972 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3944 3973 ctx = scmutil.revsingle(repo, node)
3945 3974 for f in ctx:
3946 3975 if ui.debugflag:
3947 3976 ui.write("%40s " % hex(ctx.manifest()[f]))
3948 3977 if ui.verbose:
3949 3978 ui.write(decor[ctx.flags(f)])
3950 3979 ui.write("%s\n" % f)
3951 3980
3952 3981 @command('^merge',
3953 3982 [('f', 'force', None, _('force a merge with outstanding changes')),
3954 3983 ('r', 'rev', '', _('revision to merge'), _('REV')),
3955 3984 ('P', 'preview', None,
3956 3985 _('review revisions to merge (no merge is performed)'))
3957 3986 ] + mergetoolopts,
3958 3987 _('[-P] [-f] [[-r] REV]'))
3959 3988 def merge(ui, repo, node=None, **opts):
3960 3989 """merge working directory with another revision
3961 3990
3962 3991 The current working directory is updated with all changes made in
3963 3992 the requested revision since the last common predecessor revision.
3964 3993
3965 3994 Files that changed between either parent are marked as changed for
3966 3995 the next commit and a commit must be performed before any further
3967 3996 updates to the repository are allowed. The next commit will have
3968 3997 two parents.
3969 3998
3970 3999 ``--tool`` can be used to specify the merge tool used for file
3971 4000 merges. It overrides the HGMERGE environment variable and your
3972 4001 configuration files. See :hg:`help merge-tools` for options.
3973 4002
3974 4003 If no revision is specified, the working directory's parent is a
3975 4004 head revision, and the current branch contains exactly one other
3976 4005 head, the other head is merged with by default. Otherwise, an
3977 4006 explicit revision with which to merge with must be provided.
3978 4007
3979 4008 :hg:`resolve` must be used to resolve unresolved files.
3980 4009
3981 4010 To undo an uncommitted merge, use :hg:`update --clean .` which
3982 4011 will check out a clean copy of the original merge parent, losing
3983 4012 all changes.
3984 4013
3985 4014 Returns 0 on success, 1 if there are unresolved files.
3986 4015 """
3987 4016
3988 4017 if opts.get('rev') and node:
3989 4018 raise util.Abort(_("please specify just one revision"))
3990 4019 if not node:
3991 4020 node = opts.get('rev')
3992 4021
3993 4022 if not node:
3994 4023 branch = repo[None].branch()
3995 4024 bheads = repo.branchheads(branch)
3996 4025 if len(bheads) > 2:
3997 4026 raise util.Abort(_("branch '%s' has %d heads - "
3998 4027 "please merge with an explicit rev")
3999 4028 % (branch, len(bheads)),
4000 4029 hint=_("run 'hg heads .' to see heads"))
4001 4030
4002 4031 parent = repo.dirstate.p1()
4003 4032 if len(bheads) == 1:
4004 4033 if len(repo.heads()) > 1:
4005 4034 raise util.Abort(_("branch '%s' has one head - "
4006 4035 "please merge with an explicit rev")
4007 4036 % branch,
4008 4037 hint=_("run 'hg heads' to see all heads"))
4009 4038 msg = _('there is nothing to merge')
4010 4039 if parent != repo.lookup(repo[None].branch()):
4011 4040 msg = _('%s - use "hg update" instead') % msg
4012 4041 raise util.Abort(msg)
4013 4042
4014 4043 if parent not in bheads:
4015 4044 raise util.Abort(_('working directory not at a head revision'),
4016 4045 hint=_("use 'hg update' or merge with an "
4017 4046 "explicit revision"))
4018 4047 node = parent == bheads[0] and bheads[-1] or bheads[0]
4019 4048 else:
4020 4049 node = scmutil.revsingle(repo, node).node()
4021 4050
4022 4051 if opts.get('preview'):
4023 4052 # find nodes that are ancestors of p2 but not of p1
4024 4053 p1 = repo.lookup('.')
4025 4054 p2 = repo.lookup(node)
4026 4055 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4027 4056
4028 4057 displayer = cmdutil.show_changeset(ui, repo, opts)
4029 4058 for node in nodes:
4030 4059 displayer.show(repo[node])
4031 4060 displayer.close()
4032 4061 return 0
4033 4062
4034 4063 try:
4035 4064 # ui.forcemerge is an internal variable, do not document
4036 4065 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4037 4066 return hg.merge(repo, node, force=opts.get('force'))
4038 4067 finally:
4039 4068 ui.setconfig('ui', 'forcemerge', '')
4040 4069
4041 4070 @command('outgoing|out',
4042 4071 [('f', 'force', None, _('run even when the destination is unrelated')),
4043 4072 ('r', 'rev', [],
4044 4073 _('a changeset intended to be included in the destination'), _('REV')),
4045 4074 ('n', 'newest-first', None, _('show newest record first')),
4046 4075 ('B', 'bookmarks', False, _('compare bookmarks')),
4047 4076 ('b', 'branch', [], _('a specific branch you would like to push'),
4048 4077 _('BRANCH')),
4049 4078 ] + logopts + remoteopts + subrepoopts,
4050 4079 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4051 4080 def outgoing(ui, repo, dest=None, **opts):
4052 4081 """show changesets not found in the destination
4053 4082
4054 4083 Show changesets not found in the specified destination repository
4055 4084 or the default push location. These are the changesets that would
4056 4085 be pushed if a push was requested.
4057 4086
4058 4087 See pull for details of valid destination formats.
4059 4088
4060 4089 Returns 0 if there are outgoing changes, 1 otherwise.
4061 4090 """
4062 4091
4063 4092 if opts.get('bookmarks'):
4064 4093 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4065 4094 dest, branches = hg.parseurl(dest, opts.get('branch'))
4066 4095 other = hg.peer(repo, opts, dest)
4067 4096 if 'bookmarks' not in other.listkeys('namespaces'):
4068 4097 ui.warn(_("remote doesn't support bookmarks\n"))
4069 4098 return 0
4070 4099 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4071 4100 return bookmarks.diff(ui, other, repo)
4072 4101
4073 4102 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4074 4103 try:
4075 4104 return hg.outgoing(ui, repo, dest, opts)
4076 4105 finally:
4077 4106 del repo._subtoppath
4078 4107
4079 4108 @command('parents',
4080 4109 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4081 4110 ] + templateopts,
4082 4111 _('[-r REV] [FILE]'))
4083 4112 def parents(ui, repo, file_=None, **opts):
4084 4113 """show the parents of the working directory or revision
4085 4114
4086 4115 Print the working directory's parent revisions. If a revision is
4087 4116 given via -r/--rev, the parent of that revision will be printed.
4088 4117 If a file argument is given, the revision in which the file was
4089 4118 last changed (before the working directory revision or the
4090 4119 argument to --rev if given) is printed.
4091 4120
4092 4121 Returns 0 on success.
4093 4122 """
4094 4123
4095 4124 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4096 4125
4097 4126 if file_:
4098 4127 m = scmutil.match(ctx, (file_,), opts)
4099 4128 if m.anypats() or len(m.files()) != 1:
4100 4129 raise util.Abort(_('can only specify an explicit filename'))
4101 4130 file_ = m.files()[0]
4102 4131 filenodes = []
4103 4132 for cp in ctx.parents():
4104 4133 if not cp:
4105 4134 continue
4106 4135 try:
4107 4136 filenodes.append(cp.filenode(file_))
4108 4137 except error.LookupError:
4109 4138 pass
4110 4139 if not filenodes:
4111 4140 raise util.Abort(_("'%s' not found in manifest!") % file_)
4112 4141 fl = repo.file(file_)
4113 4142 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4114 4143 else:
4115 4144 p = [cp.node() for cp in ctx.parents()]
4116 4145
4117 4146 displayer = cmdutil.show_changeset(ui, repo, opts)
4118 4147 for n in p:
4119 4148 if n != nullid:
4120 4149 displayer.show(repo[n])
4121 4150 displayer.close()
4122 4151
4123 4152 @command('paths', [], _('[NAME]'))
4124 4153 def paths(ui, repo, search=None):
4125 4154 """show aliases for remote repositories
4126 4155
4127 4156 Show definition of symbolic path name NAME. If no name is given,
4128 4157 show definition of all available names.
4129 4158
4130 4159 Option -q/--quiet suppresses all output when searching for NAME
4131 4160 and shows only the path names when listing all definitions.
4132 4161
4133 4162 Path names are defined in the [paths] section of your
4134 4163 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4135 4164 repository, ``.hg/hgrc`` is used, too.
4136 4165
4137 4166 The path names ``default`` and ``default-push`` have a special
4138 4167 meaning. When performing a push or pull operation, they are used
4139 4168 as fallbacks if no location is specified on the command-line.
4140 4169 When ``default-push`` is set, it will be used for push and
4141 4170 ``default`` will be used for pull; otherwise ``default`` is used
4142 4171 as the fallback for both. When cloning a repository, the clone
4143 4172 source is written as ``default`` in ``.hg/hgrc``. Note that
4144 4173 ``default`` and ``default-push`` apply to all inbound (e.g.
4145 4174 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4146 4175 :hg:`bundle`) operations.
4147 4176
4148 4177 See :hg:`help urls` for more information.
4149 4178
4150 4179 Returns 0 on success.
4151 4180 """
4152 4181 if search:
4153 4182 for name, path in ui.configitems("paths"):
4154 4183 if name == search:
4155 4184 ui.status("%s\n" % util.hidepassword(path))
4156 4185 return
4157 4186 if not ui.quiet:
4158 4187 ui.warn(_("not found!\n"))
4159 4188 return 1
4160 4189 else:
4161 4190 for name, path in ui.configitems("paths"):
4162 4191 if ui.quiet:
4163 4192 ui.write("%s\n" % name)
4164 4193 else:
4165 4194 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4166 4195
4167 4196 def postincoming(ui, repo, modheads, optupdate, checkout):
4168 4197 if modheads == 0:
4169 4198 return
4170 4199 if optupdate:
4171 4200 try:
4172 4201 return hg.update(repo, checkout)
4173 4202 except util.Abort, inst:
4174 4203 ui.warn(_("not updating: %s\n" % str(inst)))
4175 4204 return 0
4176 4205 if modheads > 1:
4177 4206 currentbranchheads = len(repo.branchheads())
4178 4207 if currentbranchheads == modheads:
4179 4208 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4180 4209 elif currentbranchheads > 1:
4181 4210 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
4182 4211 else:
4183 4212 ui.status(_("(run 'hg heads' to see heads)\n"))
4184 4213 else:
4185 4214 ui.status(_("(run 'hg update' to get a working copy)\n"))
4186 4215
4187 4216 @command('^pull',
4188 4217 [('u', 'update', None,
4189 4218 _('update to new branch head if changesets were pulled')),
4190 4219 ('f', 'force', None, _('run even when remote repository is unrelated')),
4191 4220 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4192 4221 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4193 4222 ('b', 'branch', [], _('a specific branch you would like to pull'),
4194 4223 _('BRANCH')),
4195 4224 ] + remoteopts,
4196 4225 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4197 4226 def pull(ui, repo, source="default", **opts):
4198 4227 """pull changes from the specified source
4199 4228
4200 4229 Pull changes from a remote repository to a local one.
4201 4230
4202 4231 This finds all changes from the repository at the specified path
4203 4232 or URL and adds them to a local repository (the current one unless
4204 4233 -R is specified). By default, this does not update the copy of the
4205 4234 project in the working directory.
4206 4235
4207 4236 Use :hg:`incoming` if you want to see what would have been added
4208 4237 by a pull at the time you issued this command. If you then decide
4209 4238 to add those changes to the repository, you should use :hg:`pull
4210 4239 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4211 4240
4212 4241 If SOURCE is omitted, the 'default' path will be used.
4213 4242 See :hg:`help urls` for more information.
4214 4243
4215 4244 Returns 0 on success, 1 if an update had unresolved files.
4216 4245 """
4217 4246 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4218 4247 other = hg.peer(repo, opts, source)
4219 4248 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4220 4249 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4221 4250
4222 4251 if opts.get('bookmark'):
4223 4252 if not revs:
4224 4253 revs = []
4225 4254 rb = other.listkeys('bookmarks')
4226 4255 for b in opts['bookmark']:
4227 4256 if b not in rb:
4228 4257 raise util.Abort(_('remote bookmark %s not found!') % b)
4229 4258 revs.append(rb[b])
4230 4259
4231 4260 if revs:
4232 4261 try:
4233 4262 revs = [other.lookup(rev) for rev in revs]
4234 4263 except error.CapabilityError:
4235 4264 err = _("other repository doesn't support revision lookup, "
4236 4265 "so a rev cannot be specified.")
4237 4266 raise util.Abort(err)
4238 4267
4239 4268 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4240 4269 bookmarks.updatefromremote(ui, repo, other)
4241 4270 if checkout:
4242 4271 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4243 4272 repo._subtoppath = source
4244 4273 try:
4245 4274 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4246 4275
4247 4276 finally:
4248 4277 del repo._subtoppath
4249 4278
4250 4279 # update specified bookmarks
4251 4280 if opts.get('bookmark'):
4252 4281 for b in opts['bookmark']:
4253 4282 # explicit pull overrides local bookmark if any
4254 4283 ui.status(_("importing bookmark %s\n") % b)
4255 4284 repo._bookmarks[b] = repo[rb[b]].node()
4256 4285 bookmarks.write(repo)
4257 4286
4258 4287 return ret
4259 4288
4260 4289 @command('^push',
4261 4290 [('f', 'force', None, _('force push')),
4262 4291 ('r', 'rev', [],
4263 4292 _('a changeset intended to be included in the destination'),
4264 4293 _('REV')),
4265 4294 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4266 4295 ('b', 'branch', [],
4267 4296 _('a specific branch you would like to push'), _('BRANCH')),
4268 4297 ('', 'new-branch', False, _('allow pushing a new branch')),
4269 4298 ] + remoteopts,
4270 4299 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4271 4300 def push(ui, repo, dest=None, **opts):
4272 4301 """push changes to the specified destination
4273 4302
4274 4303 Push changesets from the local repository to the specified
4275 4304 destination.
4276 4305
4277 4306 This operation is symmetrical to pull: it is identical to a pull
4278 4307 in the destination repository from the current one.
4279 4308
4280 4309 By default, push will not allow creation of new heads at the
4281 4310 destination, since multiple heads would make it unclear which head
4282 4311 to use. In this situation, it is recommended to pull and merge
4283 4312 before pushing.
4284 4313
4285 4314 Use --new-branch if you want to allow push to create a new named
4286 4315 branch that is not present at the destination. This allows you to
4287 4316 only create a new branch without forcing other changes.
4288 4317
4289 4318 Use -f/--force to override the default behavior and push all
4290 4319 changesets on all branches.
4291 4320
4292 4321 If -r/--rev is used, the specified revision and all its ancestors
4293 4322 will be pushed to the remote repository.
4294 4323
4295 4324 Please see :hg:`help urls` for important details about ``ssh://``
4296 4325 URLs. If DESTINATION is omitted, a default path will be used.
4297 4326
4298 4327 Returns 0 if push was successful, 1 if nothing to push.
4299 4328 """
4300 4329
4301 4330 if opts.get('bookmark'):
4302 4331 for b in opts['bookmark']:
4303 4332 # translate -B options to -r so changesets get pushed
4304 4333 if b in repo._bookmarks:
4305 4334 opts.setdefault('rev', []).append(b)
4306 4335 else:
4307 4336 # if we try to push a deleted bookmark, translate it to null
4308 4337 # this lets simultaneous -r, -b options continue working
4309 4338 opts.setdefault('rev', []).append("null")
4310 4339
4311 4340 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4312 4341 dest, branches = hg.parseurl(dest, opts.get('branch'))
4313 4342 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4314 4343 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4315 4344 other = hg.peer(repo, opts, dest)
4316 4345 if revs:
4317 4346 revs = [repo.lookup(rev) for rev in revs]
4318 4347
4319 4348 repo._subtoppath = dest
4320 4349 try:
4321 4350 # push subrepos depth-first for coherent ordering
4322 4351 c = repo['']
4323 4352 subs = c.substate # only repos that are committed
4324 4353 for s in sorted(subs):
4325 4354 if not c.sub(s).push(opts.get('force')):
4326 4355 return False
4327 4356 finally:
4328 4357 del repo._subtoppath
4329 4358 result = repo.push(other, opts.get('force'), revs=revs,
4330 4359 newbranch=opts.get('new_branch'))
4331 4360
4332 4361 result = (result == 0)
4333 4362
4334 4363 if opts.get('bookmark'):
4335 4364 rb = other.listkeys('bookmarks')
4336 4365 for b in opts['bookmark']:
4337 4366 # explicit push overrides remote bookmark if any
4338 4367 if b in repo._bookmarks:
4339 4368 ui.status(_("exporting bookmark %s\n") % b)
4340 4369 new = repo[b].hex()
4341 4370 elif b in rb:
4342 4371 ui.status(_("deleting remote bookmark %s\n") % b)
4343 4372 new = '' # delete
4344 4373 else:
4345 4374 ui.warn(_('bookmark %s does not exist on the local '
4346 4375 'or remote repository!\n') % b)
4347 4376 return 2
4348 4377 old = rb.get(b, '')
4349 4378 r = other.pushkey('bookmarks', b, old, new)
4350 4379 if not r:
4351 4380 ui.warn(_('updating bookmark %s failed!\n') % b)
4352 4381 if not result:
4353 4382 result = 2
4354 4383
4355 4384 return result
4356 4385
4357 4386 @command('recover', [])
4358 4387 def recover(ui, repo):
4359 4388 """roll back an interrupted transaction
4360 4389
4361 4390 Recover from an interrupted commit or pull.
4362 4391
4363 4392 This command tries to fix the repository status after an
4364 4393 interrupted operation. It should only be necessary when Mercurial
4365 4394 suggests it.
4366 4395
4367 4396 Returns 0 if successful, 1 if nothing to recover or verify fails.
4368 4397 """
4369 4398 if repo.recover():
4370 4399 return hg.verify(repo)
4371 4400 return 1
4372 4401
4373 4402 @command('^remove|rm',
4374 4403 [('A', 'after', None, _('record delete for missing files')),
4375 4404 ('f', 'force', None,
4376 4405 _('remove (and delete) file even if added or modified')),
4377 4406 ] + walkopts,
4378 4407 _('[OPTION]... FILE...'))
4379 4408 def remove(ui, repo, *pats, **opts):
4380 4409 """remove the specified files on the next commit
4381 4410
4382 4411 Schedule the indicated files for removal from the current branch.
4383 4412
4384 4413 This command schedules the files to be removed at the next commit.
4385 4414 To undo a remove before that, see :hg:`revert`. To undo added
4386 4415 files, see :hg:`forget`.
4387 4416
4388 4417 .. container:: verbose
4389 4418
4390 4419 -A/--after can be used to remove only files that have already
4391 4420 been deleted, -f/--force can be used to force deletion, and -Af
4392 4421 can be used to remove files from the next revision without
4393 4422 deleting them from the working directory.
4394 4423
4395 4424 The following table details the behavior of remove for different
4396 4425 file states (columns) and option combinations (rows). The file
4397 4426 states are Added [A], Clean [C], Modified [M] and Missing [!]
4398 4427 (as reported by :hg:`status`). The actions are Warn, Remove
4399 4428 (from branch) and Delete (from disk):
4400 4429
4401 4430 ======= == == == ==
4402 4431 A C M !
4403 4432 ======= == == == ==
4404 4433 none W RD W R
4405 4434 -f R RD RD R
4406 4435 -A W W W R
4407 4436 -Af R R R R
4408 4437 ======= == == == ==
4409 4438
4410 4439 Note that remove never deletes files in Added [A] state from the
4411 4440 working directory, not even if option --force is specified.
4412 4441
4413 4442 Returns 0 on success, 1 if any warnings encountered.
4414 4443 """
4415 4444
4416 4445 ret = 0
4417 4446 after, force = opts.get('after'), opts.get('force')
4418 4447 if not pats and not after:
4419 4448 raise util.Abort(_('no files specified'))
4420 4449
4421 4450 m = scmutil.match(repo[None], pats, opts)
4422 4451 s = repo.status(match=m, clean=True)
4423 4452 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4424 4453
4425 4454 for f in m.files():
4426 4455 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4427 4456 if os.path.exists(m.rel(f)):
4428 4457 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4429 4458 ret = 1
4430 4459
4431 4460 if force:
4432 4461 list = modified + deleted + clean + added
4433 4462 elif after:
4434 4463 list = deleted
4435 4464 for f in modified + added + clean:
4436 4465 ui.warn(_('not removing %s: file still exists (use -f'
4437 4466 ' to force removal)\n') % m.rel(f))
4438 4467 ret = 1
4439 4468 else:
4440 4469 list = deleted + clean
4441 4470 for f in modified:
4442 4471 ui.warn(_('not removing %s: file is modified (use -f'
4443 4472 ' to force removal)\n') % m.rel(f))
4444 4473 ret = 1
4445 4474 for f in added:
4446 4475 ui.warn(_('not removing %s: file has been marked for add'
4447 4476 ' (use forget to undo)\n') % m.rel(f))
4448 4477 ret = 1
4449 4478
4450 4479 for f in sorted(list):
4451 4480 if ui.verbose or not m.exact(f):
4452 4481 ui.status(_('removing %s\n') % m.rel(f))
4453 4482
4454 4483 wlock = repo.wlock()
4455 4484 try:
4456 4485 if not after:
4457 4486 for f in list:
4458 4487 if f in added:
4459 4488 continue # we never unlink added files on remove
4460 4489 try:
4461 4490 util.unlinkpath(repo.wjoin(f))
4462 4491 except OSError, inst:
4463 4492 if inst.errno != errno.ENOENT:
4464 4493 raise
4465 4494 repo[None].forget(list)
4466 4495 finally:
4467 4496 wlock.release()
4468 4497
4469 4498 return ret
4470 4499
4471 4500 @command('rename|move|mv',
4472 4501 [('A', 'after', None, _('record a rename that has already occurred')),
4473 4502 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4474 4503 ] + walkopts + dryrunopts,
4475 4504 _('[OPTION]... SOURCE... DEST'))
4476 4505 def rename(ui, repo, *pats, **opts):
4477 4506 """rename files; equivalent of copy + remove
4478 4507
4479 4508 Mark dest as copies of sources; mark sources for deletion. If dest
4480 4509 is a directory, copies are put in that directory. If dest is a
4481 4510 file, there can only be one source.
4482 4511
4483 4512 By default, this command copies the contents of files as they
4484 4513 exist in the working directory. If invoked with -A/--after, the
4485 4514 operation is recorded, but no copying is performed.
4486 4515
4487 4516 This command takes effect at the next commit. To undo a rename
4488 4517 before that, see :hg:`revert`.
4489 4518
4490 4519 Returns 0 on success, 1 if errors are encountered.
4491 4520 """
4492 4521 wlock = repo.wlock(False)
4493 4522 try:
4494 4523 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4495 4524 finally:
4496 4525 wlock.release()
4497 4526
4498 4527 @command('resolve',
4499 4528 [('a', 'all', None, _('select all unresolved files')),
4500 4529 ('l', 'list', None, _('list state of files needing merge')),
4501 4530 ('m', 'mark', None, _('mark files as resolved')),
4502 4531 ('u', 'unmark', None, _('mark files as unresolved')),
4503 4532 ('n', 'no-status', None, _('hide status prefix'))]
4504 4533 + mergetoolopts + walkopts,
4505 4534 _('[OPTION]... [FILE]...'))
4506 4535 def resolve(ui, repo, *pats, **opts):
4507 4536 """redo merges or set/view the merge status of files
4508 4537
4509 4538 Merges with unresolved conflicts are often the result of
4510 4539 non-interactive merging using the ``internal:merge`` configuration
4511 4540 setting, or a command-line merge tool like ``diff3``. The resolve
4512 4541 command is used to manage the files involved in a merge, after
4513 4542 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4514 4543 working directory must have two parents).
4515 4544
4516 4545 The resolve command can be used in the following ways:
4517 4546
4518 4547 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4519 4548 files, discarding any previous merge attempts. Re-merging is not
4520 4549 performed for files already marked as resolved. Use ``--all/-a``
4521 4550 to select all unresolved files. ``--tool`` can be used to specify
4522 4551 the merge tool used for the given files. It overrides the HGMERGE
4523 4552 environment variable and your configuration files. Previous file
4524 4553 contents are saved with a ``.orig`` suffix.
4525 4554
4526 4555 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4527 4556 (e.g. after having manually fixed-up the files). The default is
4528 4557 to mark all unresolved files.
4529 4558
4530 4559 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4531 4560 default is to mark all resolved files.
4532 4561
4533 4562 - :hg:`resolve -l`: list files which had or still have conflicts.
4534 4563 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4535 4564
4536 4565 Note that Mercurial will not let you commit files with unresolved
4537 4566 merge conflicts. You must use :hg:`resolve -m ...` before you can
4538 4567 commit after a conflicting merge.
4539 4568
4540 4569 Returns 0 on success, 1 if any files fail a resolve attempt.
4541 4570 """
4542 4571
4543 4572 all, mark, unmark, show, nostatus = \
4544 4573 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4545 4574
4546 4575 if (show and (mark or unmark)) or (mark and unmark):
4547 4576 raise util.Abort(_("too many options specified"))
4548 4577 if pats and all:
4549 4578 raise util.Abort(_("can't specify --all and patterns"))
4550 4579 if not (all or pats or show or mark or unmark):
4551 4580 raise util.Abort(_('no files or directories specified; '
4552 4581 'use --all to remerge all files'))
4553 4582
4554 4583 ms = mergemod.mergestate(repo)
4555 4584 m = scmutil.match(repo[None], pats, opts)
4556 4585 ret = 0
4557 4586
4558 4587 for f in ms:
4559 4588 if m(f):
4560 4589 if show:
4561 4590 if nostatus:
4562 4591 ui.write("%s\n" % f)
4563 4592 else:
4564 4593 ui.write("%s %s\n" % (ms[f].upper(), f),
4565 4594 label='resolve.' +
4566 4595 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4567 4596 elif mark:
4568 4597 ms.mark(f, "r")
4569 4598 elif unmark:
4570 4599 ms.mark(f, "u")
4571 4600 else:
4572 4601 wctx = repo[None]
4573 4602 mctx = wctx.parents()[-1]
4574 4603
4575 4604 # backup pre-resolve (merge uses .orig for its own purposes)
4576 4605 a = repo.wjoin(f)
4577 4606 util.copyfile(a, a + ".resolve")
4578 4607
4579 4608 try:
4580 4609 # resolve file
4581 4610 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4582 4611 if ms.resolve(f, wctx, mctx):
4583 4612 ret = 1
4584 4613 finally:
4585 4614 ui.setconfig('ui', 'forcemerge', '')
4586 4615
4587 4616 # replace filemerge's .orig file with our resolve file
4588 4617 util.rename(a + ".resolve", a + ".orig")
4589 4618
4590 4619 ms.commit()
4591 4620 return ret
4592 4621
4593 4622 @command('revert',
4594 4623 [('a', 'all', None, _('revert all changes when no arguments given')),
4595 4624 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4596 4625 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4597 4626 ('C', 'no-backup', None, _('do not save backup copies of files')),
4598 4627 ] + walkopts + dryrunopts,
4599 4628 _('[OPTION]... [-r REV] [NAME]...'))
4600 4629 def revert(ui, repo, *pats, **opts):
4601 4630 """restore files to their checkout state
4602 4631
4603 4632 .. note::
4604 4633 To check out earlier revisions, you should use :hg:`update REV`.
4605 4634 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4606 4635
4607 4636 With no revision specified, revert the specified files or directories
4608 4637 to the contents they had in the parent of the working directory.
4609 4638 This restores the contents of files to an unmodified
4610 4639 state and unschedules adds, removes, copies, and renames. If the
4611 4640 working directory has two parents, you must explicitly specify a
4612 4641 revision.
4613 4642
4614 4643 Using the -r/--rev or -d/--date options, revert the given files or
4615 4644 directories to their states as of a specific revision. Because
4616 4645 revert does not change the working directory parents, this will
4617 4646 cause these files to appear modified. This can be helpful to "back
4618 4647 out" some or all of an earlier change. See :hg:`backout` for a
4619 4648 related method.
4620 4649
4621 4650 Modified files are saved with a .orig suffix before reverting.
4622 4651 To disable these backups, use --no-backup.
4623 4652
4624 4653 See :hg:`help dates` for a list of formats valid for -d/--date.
4625 4654
4626 4655 Returns 0 on success.
4627 4656 """
4628 4657
4629 4658 if opts.get("date"):
4630 4659 if opts.get("rev"):
4631 4660 raise util.Abort(_("you can't specify a revision and a date"))
4632 4661 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4633 4662
4634 4663 parent, p2 = repo.dirstate.parents()
4635 4664 if not opts.get('rev') and p2 != nullid:
4636 4665 # revert after merge is a trap for new users (issue2915)
4637 4666 raise util.Abort(_('uncommitted merge with no revision specified'),
4638 4667 hint=_('use "hg update" or see "hg help revert"'))
4639 4668
4640 4669 ctx = scmutil.revsingle(repo, opts.get('rev'))
4641 4670 node = ctx.node()
4642 4671
4643 4672 if not pats and not opts.get('all'):
4644 4673 msg = _("no files or directories specified")
4645 4674 if p2 != nullid:
4646 4675 hint = _("uncommitted merge, use --all to discard all changes,"
4647 4676 " or 'hg update -C .' to abort the merge")
4648 4677 raise util.Abort(msg, hint=hint)
4649 4678 dirty = util.any(repo.status())
4650 4679 if node != parent:
4651 4680 if dirty:
4652 4681 hint = _("uncommitted changes, use --all to discard all"
4653 4682 " changes, or 'hg update %s' to update") % ctx.rev()
4654 4683 else:
4655 4684 hint = _("use --all to revert all files,"
4656 4685 " or 'hg update %s' to update") % ctx.rev()
4657 4686 elif dirty:
4658 4687 hint = _("uncommitted changes, use --all to discard all changes")
4659 4688 else:
4660 4689 hint = _("use --all to revert all files")
4661 4690 raise util.Abort(msg, hint=hint)
4662 4691
4663 4692 mf = ctx.manifest()
4664 4693 if node == parent:
4665 4694 pmf = mf
4666 4695 else:
4667 4696 pmf = None
4668 4697
4669 4698 # need all matching names in dirstate and manifest of target rev,
4670 4699 # so have to walk both. do not print errors if files exist in one
4671 4700 # but not other.
4672 4701
4673 4702 names = {}
4674 4703
4675 4704 wlock = repo.wlock()
4676 4705 try:
4677 4706 # walk dirstate.
4678 4707
4679 4708 m = scmutil.match(repo[None], pats, opts)
4680 4709 m.bad = lambda x, y: False
4681 4710 for abs in repo.walk(m):
4682 4711 names[abs] = m.rel(abs), m.exact(abs)
4683 4712
4684 4713 # walk target manifest.
4685 4714
4686 4715 def badfn(path, msg):
4687 4716 if path in names:
4688 4717 return
4689 4718 if path in repo[node].substate:
4690 4719 ui.warn("%s: %s\n" % (m.rel(path),
4691 4720 'reverting subrepos is unsupported'))
4692 4721 return
4693 4722 path_ = path + '/'
4694 4723 for f in names:
4695 4724 if f.startswith(path_):
4696 4725 return
4697 4726 ui.warn("%s: %s\n" % (m.rel(path), msg))
4698 4727
4699 4728 m = scmutil.match(repo[node], pats, opts)
4700 4729 m.bad = badfn
4701 4730 for abs in repo[node].walk(m):
4702 4731 if abs not in names:
4703 4732 names[abs] = m.rel(abs), m.exact(abs)
4704 4733
4705 4734 m = scmutil.matchfiles(repo, names)
4706 4735 changes = repo.status(match=m)[:4]
4707 4736 modified, added, removed, deleted = map(set, changes)
4708 4737
4709 4738 # if f is a rename, also revert the source
4710 4739 cwd = repo.getcwd()
4711 4740 for f in added:
4712 4741 src = repo.dirstate.copied(f)
4713 4742 if src and src not in names and repo.dirstate[src] == 'r':
4714 4743 removed.add(src)
4715 4744 names[src] = (repo.pathto(src, cwd), True)
4716 4745
4717 4746 def removeforget(abs):
4718 4747 if repo.dirstate[abs] == 'a':
4719 4748 return _('forgetting %s\n')
4720 4749 return _('removing %s\n')
4721 4750
4722 4751 revert = ([], _('reverting %s\n'))
4723 4752 add = ([], _('adding %s\n'))
4724 4753 remove = ([], removeforget)
4725 4754 undelete = ([], _('undeleting %s\n'))
4726 4755
4727 4756 disptable = (
4728 4757 # dispatch table:
4729 4758 # file state
4730 4759 # action if in target manifest
4731 4760 # action if not in target manifest
4732 4761 # make backup if in target manifest
4733 4762 # make backup if not in target manifest
4734 4763 (modified, revert, remove, True, True),
4735 4764 (added, revert, remove, True, False),
4736 4765 (removed, undelete, None, False, False),
4737 4766 (deleted, revert, remove, False, False),
4738 4767 )
4739 4768
4740 4769 for abs, (rel, exact) in sorted(names.items()):
4741 4770 mfentry = mf.get(abs)
4742 4771 target = repo.wjoin(abs)
4743 4772 def handle(xlist, dobackup):
4744 4773 xlist[0].append(abs)
4745 4774 if (dobackup and not opts.get('no_backup') and
4746 4775 os.path.lexists(target)):
4747 4776 bakname = "%s.orig" % rel
4748 4777 ui.note(_('saving current version of %s as %s\n') %
4749 4778 (rel, bakname))
4750 4779 if not opts.get('dry_run'):
4751 4780 util.rename(target, bakname)
4752 4781 if ui.verbose or not exact:
4753 4782 msg = xlist[1]
4754 4783 if not isinstance(msg, basestring):
4755 4784 msg = msg(abs)
4756 4785 ui.status(msg % rel)
4757 4786 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4758 4787 if abs not in table:
4759 4788 continue
4760 4789 # file has changed in dirstate
4761 4790 if mfentry:
4762 4791 handle(hitlist, backuphit)
4763 4792 elif misslist is not None:
4764 4793 handle(misslist, backupmiss)
4765 4794 break
4766 4795 else:
4767 4796 if abs not in repo.dirstate:
4768 4797 if mfentry:
4769 4798 handle(add, True)
4770 4799 elif exact:
4771 4800 ui.warn(_('file not managed: %s\n') % rel)
4772 4801 continue
4773 4802 # file has not changed in dirstate
4774 4803 if node == parent:
4775 4804 if exact:
4776 4805 ui.warn(_('no changes needed to %s\n') % rel)
4777 4806 continue
4778 4807 if pmf is None:
4779 4808 # only need parent manifest in this unlikely case,
4780 4809 # so do not read by default
4781 4810 pmf = repo[parent].manifest()
4782 4811 if abs in pmf and mfentry:
4783 4812 # if version of file is same in parent and target
4784 4813 # manifests, do nothing
4785 4814 if (pmf[abs] != mfentry or
4786 4815 pmf.flags(abs) != mf.flags(abs)):
4787 4816 handle(revert, False)
4788 4817 else:
4789 4818 handle(remove, False)
4790 4819
4791 4820 if not opts.get('dry_run'):
4792 4821 def checkout(f):
4793 4822 fc = ctx[f]
4794 4823 repo.wwrite(f, fc.data(), fc.flags())
4795 4824
4796 4825 audit_path = scmutil.pathauditor(repo.root)
4797 4826 for f in remove[0]:
4798 4827 if repo.dirstate[f] == 'a':
4799 4828 repo.dirstate.drop(f)
4800 4829 continue
4801 4830 audit_path(f)
4802 4831 try:
4803 4832 util.unlinkpath(repo.wjoin(f))
4804 4833 except OSError:
4805 4834 pass
4806 4835 repo.dirstate.remove(f)
4807 4836
4808 4837 normal = None
4809 4838 if node == parent:
4810 4839 # We're reverting to our parent. If possible, we'd like status
4811 4840 # to report the file as clean. We have to use normallookup for
4812 4841 # merges to avoid losing information about merged/dirty files.
4813 4842 if p2 != nullid:
4814 4843 normal = repo.dirstate.normallookup
4815 4844 else:
4816 4845 normal = repo.dirstate.normal
4817 4846 for f in revert[0]:
4818 4847 checkout(f)
4819 4848 if normal:
4820 4849 normal(f)
4821 4850
4822 4851 for f in add[0]:
4823 4852 checkout(f)
4824 4853 repo.dirstate.add(f)
4825 4854
4826 4855 normal = repo.dirstate.normallookup
4827 4856 if node == parent and p2 == nullid:
4828 4857 normal = repo.dirstate.normal
4829 4858 for f in undelete[0]:
4830 4859 checkout(f)
4831 4860 normal(f)
4832 4861
4833 4862 finally:
4834 4863 wlock.release()
4835 4864
4836 4865 @command('rollback', dryrunopts +
4837 4866 [('f', 'force', False, _('ignore safety measures'))])
4838 4867 def rollback(ui, repo, **opts):
4839 4868 """roll back the last transaction (dangerous)
4840 4869
4841 4870 This command should be used with care. There is only one level of
4842 4871 rollback, and there is no way to undo a rollback. It will also
4843 4872 restore the dirstate at the time of the last transaction, losing
4844 4873 any dirstate changes since that time. This command does not alter
4845 4874 the working directory.
4846 4875
4847 4876 Transactions are used to encapsulate the effects of all commands
4848 4877 that create new changesets or propagate existing changesets into a
4849 4878 repository. For example, the following commands are transactional,
4850 4879 and their effects can be rolled back:
4851 4880
4852 4881 - commit
4853 4882 - import
4854 4883 - pull
4855 4884 - push (with this repository as the destination)
4856 4885 - unbundle
4857 4886
4858 4887 It's possible to lose data with rollback: commit, update back to
4859 4888 an older changeset, and then rollback. The update removes the
4860 4889 changes you committed from the working directory, and rollback
4861 4890 removes them from history. To avoid data loss, you must pass
4862 4891 --force in this case.
4863 4892
4864 4893 This command is not intended for use on public repositories. Once
4865 4894 changes are visible for pull by other users, rolling a transaction
4866 4895 back locally is ineffective (someone else may already have pulled
4867 4896 the changes). Furthermore, a race is possible with readers of the
4868 4897 repository; for example an in-progress pull from the repository
4869 4898 may fail if a rollback is performed.
4870 4899
4871 4900 Returns 0 on success, 1 if no rollback data is available.
4872 4901 """
4873 4902 return repo.rollback(dryrun=opts.get('dry_run'),
4874 4903 force=opts.get('force'))
4875 4904
4876 4905 @command('root', [])
4877 4906 def root(ui, repo):
4878 4907 """print the root (top) of the current working directory
4879 4908
4880 4909 Print the root directory of the current repository.
4881 4910
4882 4911 Returns 0 on success.
4883 4912 """
4884 4913 ui.write(repo.root + "\n")
4885 4914
4886 4915 @command('^serve',
4887 4916 [('A', 'accesslog', '', _('name of access log file to write to'),
4888 4917 _('FILE')),
4889 4918 ('d', 'daemon', None, _('run server in background')),
4890 4919 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4891 4920 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4892 4921 # use string type, then we can check if something was passed
4893 4922 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4894 4923 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4895 4924 _('ADDR')),
4896 4925 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4897 4926 _('PREFIX')),
4898 4927 ('n', 'name', '',
4899 4928 _('name to show in web pages (default: working directory)'), _('NAME')),
4900 4929 ('', 'web-conf', '',
4901 4930 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4902 4931 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4903 4932 _('FILE')),
4904 4933 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4905 4934 ('', 'stdio', None, _('for remote clients')),
4906 4935 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4907 4936 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4908 4937 ('', 'style', '', _('template style to use'), _('STYLE')),
4909 4938 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4910 4939 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4911 4940 _('[OPTION]...'))
4912 4941 def serve(ui, repo, **opts):
4913 4942 """start stand-alone webserver
4914 4943
4915 4944 Start a local HTTP repository browser and pull server. You can use
4916 4945 this for ad-hoc sharing and browsing of repositories. It is
4917 4946 recommended to use a real web server to serve a repository for
4918 4947 longer periods of time.
4919 4948
4920 4949 Please note that the server does not implement access control.
4921 4950 This means that, by default, anybody can read from the server and
4922 4951 nobody can write to it by default. Set the ``web.allow_push``
4923 4952 option to ``*`` to allow everybody to push to the server. You
4924 4953 should use a real web server if you need to authenticate users.
4925 4954
4926 4955 By default, the server logs accesses to stdout and errors to
4927 4956 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4928 4957 files.
4929 4958
4930 4959 To have the server choose a free port number to listen on, specify
4931 4960 a port number of 0; in this case, the server will print the port
4932 4961 number it uses.
4933 4962
4934 4963 Returns 0 on success.
4935 4964 """
4936 4965
4937 4966 if opts["stdio"] and opts["cmdserver"]:
4938 4967 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4939 4968
4940 4969 def checkrepo():
4941 4970 if repo is None:
4942 4971 raise error.RepoError(_("There is no Mercurial repository here"
4943 4972 " (.hg not found)"))
4944 4973
4945 4974 if opts["stdio"]:
4946 4975 checkrepo()
4947 4976 s = sshserver.sshserver(ui, repo)
4948 4977 s.serve_forever()
4949 4978
4950 4979 if opts["cmdserver"]:
4951 4980 checkrepo()
4952 4981 s = commandserver.server(ui, repo, opts["cmdserver"])
4953 4982 return s.serve()
4954 4983
4955 4984 # this way we can check if something was given in the command-line
4956 4985 if opts.get('port'):
4957 4986 opts['port'] = util.getport(opts.get('port'))
4958 4987
4959 4988 baseui = repo and repo.baseui or ui
4960 4989 optlist = ("name templates style address port prefix ipv6"
4961 4990 " accesslog errorlog certificate encoding")
4962 4991 for o in optlist.split():
4963 4992 val = opts.get(o, '')
4964 4993 if val in (None, ''): # should check against default options instead
4965 4994 continue
4966 4995 baseui.setconfig("web", o, val)
4967 4996 if repo and repo.ui != baseui:
4968 4997 repo.ui.setconfig("web", o, val)
4969 4998
4970 4999 o = opts.get('web_conf') or opts.get('webdir_conf')
4971 5000 if not o:
4972 5001 if not repo:
4973 5002 raise error.RepoError(_("There is no Mercurial repository"
4974 5003 " here (.hg not found)"))
4975 5004 o = repo.root
4976 5005
4977 5006 app = hgweb.hgweb(o, baseui=ui)
4978 5007
4979 5008 class service(object):
4980 5009 def init(self):
4981 5010 util.setsignalhandler()
4982 5011 self.httpd = hgweb.server.create_server(ui, app)
4983 5012
4984 5013 if opts['port'] and not ui.verbose:
4985 5014 return
4986 5015
4987 5016 if self.httpd.prefix:
4988 5017 prefix = self.httpd.prefix.strip('/') + '/'
4989 5018 else:
4990 5019 prefix = ''
4991 5020
4992 5021 port = ':%d' % self.httpd.port
4993 5022 if port == ':80':
4994 5023 port = ''
4995 5024
4996 5025 bindaddr = self.httpd.addr
4997 5026 if bindaddr == '0.0.0.0':
4998 5027 bindaddr = '*'
4999 5028 elif ':' in bindaddr: # IPv6
5000 5029 bindaddr = '[%s]' % bindaddr
5001 5030
5002 5031 fqaddr = self.httpd.fqaddr
5003 5032 if ':' in fqaddr:
5004 5033 fqaddr = '[%s]' % fqaddr
5005 5034 if opts['port']:
5006 5035 write = ui.status
5007 5036 else:
5008 5037 write = ui.write
5009 5038 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5010 5039 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5011 5040
5012 5041 def run(self):
5013 5042 self.httpd.serve_forever()
5014 5043
5015 5044 service = service()
5016 5045
5017 5046 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5018 5047
5019 5048 @command('showconfig|debugconfig',
5020 5049 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5021 5050 _('[-u] [NAME]...'))
5022 5051 def showconfig(ui, repo, *values, **opts):
5023 5052 """show combined config settings from all hgrc files
5024 5053
5025 5054 With no arguments, print names and values of all config items.
5026 5055
5027 5056 With one argument of the form section.name, print just the value
5028 5057 of that config item.
5029 5058
5030 5059 With multiple arguments, print names and values of all config
5031 5060 items with matching section names.
5032 5061
5033 5062 With --debug, the source (filename and line number) is printed
5034 5063 for each config item.
5035 5064
5036 5065 Returns 0 on success.
5037 5066 """
5038 5067
5039 5068 for f in scmutil.rcpath():
5040 5069 ui.debug('read config from: %s\n' % f)
5041 5070 untrusted = bool(opts.get('untrusted'))
5042 5071 if values:
5043 5072 sections = [v for v in values if '.' not in v]
5044 5073 items = [v for v in values if '.' in v]
5045 5074 if len(items) > 1 or items and sections:
5046 5075 raise util.Abort(_('only one config item permitted'))
5047 5076 for section, name, value in ui.walkconfig(untrusted=untrusted):
5048 5077 value = str(value).replace('\n', '\\n')
5049 5078 sectname = section + '.' + name
5050 5079 if values:
5051 5080 for v in values:
5052 5081 if v == section:
5053 5082 ui.debug('%s: ' %
5054 5083 ui.configsource(section, name, untrusted))
5055 5084 ui.write('%s=%s\n' % (sectname, value))
5056 5085 elif v == sectname:
5057 5086 ui.debug('%s: ' %
5058 5087 ui.configsource(section, name, untrusted))
5059 5088 ui.write(value, '\n')
5060 5089 else:
5061 5090 ui.debug('%s: ' %
5062 5091 ui.configsource(section, name, untrusted))
5063 5092 ui.write('%s=%s\n' % (sectname, value))
5064 5093
5065 5094 @command('^status|st',
5066 5095 [('A', 'all', None, _('show status of all files')),
5067 5096 ('m', 'modified', None, _('show only modified files')),
5068 5097 ('a', 'added', None, _('show only added files')),
5069 5098 ('r', 'removed', None, _('show only removed files')),
5070 5099 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5071 5100 ('c', 'clean', None, _('show only files without changes')),
5072 5101 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5073 5102 ('i', 'ignored', None, _('show only ignored files')),
5074 5103 ('n', 'no-status', None, _('hide status prefix')),
5075 5104 ('C', 'copies', None, _('show source of copied files')),
5076 5105 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5077 5106 ('', 'rev', [], _('show difference from revision'), _('REV')),
5078 5107 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5079 5108 ] + walkopts + subrepoopts,
5080 5109 _('[OPTION]... [FILE]...'))
5081 5110 def status(ui, repo, *pats, **opts):
5082 5111 """show changed files in the working directory
5083 5112
5084 5113 Show status of files in the repository. If names are given, only
5085 5114 files that match are shown. Files that are clean or ignored or
5086 5115 the source of a copy/move operation, are not listed unless
5087 5116 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5088 5117 Unless options described with "show only ..." are given, the
5089 5118 options -mardu are used.
5090 5119
5091 5120 Option -q/--quiet hides untracked (unknown and ignored) files
5092 5121 unless explicitly requested with -u/--unknown or -i/--ignored.
5093 5122
5094 5123 .. note::
5095 5124 status may appear to disagree with diff if permissions have
5096 5125 changed or a merge has occurred. The standard diff format does
5097 5126 not report permission changes and diff only reports changes
5098 5127 relative to one merge parent.
5099 5128
5100 5129 If one revision is given, it is used as the base revision.
5101 5130 If two revisions are given, the differences between them are
5102 5131 shown. The --change option can also be used as a shortcut to list
5103 5132 the changed files of a revision from its first parent.
5104 5133
5105 5134 The codes used to show the status of files are::
5106 5135
5107 5136 M = modified
5108 5137 A = added
5109 5138 R = removed
5110 5139 C = clean
5111 5140 ! = missing (deleted by non-hg command, but still tracked)
5112 5141 ? = not tracked
5113 5142 I = ignored
5114 5143 = origin of the previous file listed as A (added)
5115 5144
5116 5145 .. container:: verbose
5117 5146
5118 5147 Examples:
5119 5148
5120 5149 - show changes in the working directory relative to a changeset:
5121 5150
5122 5151 hg status --rev 9353
5123 5152
5124 5153 - show all changes including copies in an existing changeset::
5125 5154
5126 5155 hg status --copies --change 9353
5127 5156
5128 5157 - get a NUL separated list of added files, suitable for xargs::
5129 5158
5130 5159 hg status -an0
5131 5160
5132 5161 Returns 0 on success.
5133 5162 """
5134 5163
5135 5164 revs = opts.get('rev')
5136 5165 change = opts.get('change')
5137 5166
5138 5167 if revs and change:
5139 5168 msg = _('cannot specify --rev and --change at the same time')
5140 5169 raise util.Abort(msg)
5141 5170 elif change:
5142 5171 node2 = repo.lookup(change)
5143 5172 node1 = repo[node2].p1().node()
5144 5173 else:
5145 5174 node1, node2 = scmutil.revpair(repo, revs)
5146 5175
5147 5176 cwd = (pats and repo.getcwd()) or ''
5148 5177 end = opts.get('print0') and '\0' or '\n'
5149 5178 copy = {}
5150 5179 states = 'modified added removed deleted unknown ignored clean'.split()
5151 5180 show = [k for k in states if opts.get(k)]
5152 5181 if opts.get('all'):
5153 5182 show += ui.quiet and (states[:4] + ['clean']) or states
5154 5183 if not show:
5155 5184 show = ui.quiet and states[:4] or states[:5]
5156 5185
5157 5186 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5158 5187 'ignored' in show, 'clean' in show, 'unknown' in show,
5159 5188 opts.get('subrepos'))
5160 5189 changestates = zip(states, 'MAR!?IC', stat)
5161 5190
5162 5191 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5163 5192 ctxn = repo[nullid]
5164 5193 ctx1 = repo[node1]
5165 5194 ctx2 = repo[node2]
5166 5195 added = stat[1]
5167 5196 if node2 is None:
5168 5197 added = stat[0] + stat[1] # merged?
5169 5198
5170 5199 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
5171 5200 if k in added:
5172 5201 copy[k] = v
5173 5202 elif v in added:
5174 5203 copy[v] = k
5175 5204
5176 5205 for state, char, files in changestates:
5177 5206 if state in show:
5178 5207 format = "%s %%s%s" % (char, end)
5179 5208 if opts.get('no_status'):
5180 5209 format = "%%s%s" % end
5181 5210
5182 5211 for f in files:
5183 5212 ui.write(format % repo.pathto(f, cwd),
5184 5213 label='status.' + state)
5185 5214 if f in copy:
5186 5215 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
5187 5216 label='status.copied')
5188 5217
5189 5218 @command('^summary|sum',
5190 5219 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5191 5220 def summary(ui, repo, **opts):
5192 5221 """summarize working directory state
5193 5222
5194 5223 This generates a brief summary of the working directory state,
5195 5224 including parents, branch, commit status, and available updates.
5196 5225
5197 5226 With the --remote option, this will check the default paths for
5198 5227 incoming and outgoing changes. This can be time-consuming.
5199 5228
5200 5229 Returns 0 on success.
5201 5230 """
5202 5231
5203 5232 ctx = repo[None]
5204 5233 parents = ctx.parents()
5205 5234 pnode = parents[0].node()
5206 5235 marks = []
5207 5236
5208 5237 for p in parents:
5209 5238 # label with log.changeset (instead of log.parent) since this
5210 5239 # shows a working directory parent *changeset*:
5211 5240 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5212 5241 label='log.changeset')
5213 5242 ui.write(' '.join(p.tags()), label='log.tag')
5214 5243 if p.bookmarks():
5215 5244 marks.extend(p.bookmarks())
5216 5245 if p.rev() == -1:
5217 5246 if not len(repo):
5218 5247 ui.write(_(' (empty repository)'))
5219 5248 else:
5220 5249 ui.write(_(' (no revision checked out)'))
5221 5250 ui.write('\n')
5222 5251 if p.description():
5223 5252 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5224 5253 label='log.summary')
5225 5254
5226 5255 branch = ctx.branch()
5227 5256 bheads = repo.branchheads(branch)
5228 5257 m = _('branch: %s\n') % branch
5229 5258 if branch != 'default':
5230 5259 ui.write(m, label='log.branch')
5231 5260 else:
5232 5261 ui.status(m, label='log.branch')
5233 5262
5234 5263 if marks:
5235 5264 current = repo._bookmarkcurrent
5236 5265 ui.write(_('bookmarks:'), label='log.bookmark')
5237 5266 if current is not None:
5238 5267 try:
5239 5268 marks.remove(current)
5240 5269 ui.write(' *' + current, label='bookmarks.current')
5241 5270 except ValueError:
5242 5271 # current bookmark not in parent ctx marks
5243 5272 pass
5244 5273 for m in marks:
5245 5274 ui.write(' ' + m, label='log.bookmark')
5246 5275 ui.write('\n', label='log.bookmark')
5247 5276
5248 5277 st = list(repo.status(unknown=True))[:6]
5249 5278
5250 5279 c = repo.dirstate.copies()
5251 5280 copied, renamed = [], []
5252 5281 for d, s in c.iteritems():
5253 5282 if s in st[2]:
5254 5283 st[2].remove(s)
5255 5284 renamed.append(d)
5256 5285 else:
5257 5286 copied.append(d)
5258 5287 if d in st[1]:
5259 5288 st[1].remove(d)
5260 5289 st.insert(3, renamed)
5261 5290 st.insert(4, copied)
5262 5291
5263 5292 ms = mergemod.mergestate(repo)
5264 5293 st.append([f for f in ms if ms[f] == 'u'])
5265 5294
5266 5295 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5267 5296 st.append(subs)
5268 5297
5269 5298 labels = [ui.label(_('%d modified'), 'status.modified'),
5270 5299 ui.label(_('%d added'), 'status.added'),
5271 5300 ui.label(_('%d removed'), 'status.removed'),
5272 5301 ui.label(_('%d renamed'), 'status.copied'),
5273 5302 ui.label(_('%d copied'), 'status.copied'),
5274 5303 ui.label(_('%d deleted'), 'status.deleted'),
5275 5304 ui.label(_('%d unknown'), 'status.unknown'),
5276 5305 ui.label(_('%d ignored'), 'status.ignored'),
5277 5306 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5278 5307 ui.label(_('%d subrepos'), 'status.modified')]
5279 5308 t = []
5280 5309 for s, l in zip(st, labels):
5281 5310 if s:
5282 5311 t.append(l % len(s))
5283 5312
5284 5313 t = ', '.join(t)
5285 5314 cleanworkdir = False
5286 5315
5287 5316 if len(parents) > 1:
5288 5317 t += _(' (merge)')
5289 5318 elif branch != parents[0].branch():
5290 5319 t += _(' (new branch)')
5291 5320 elif (parents[0].extra().get('close') and
5292 5321 pnode in repo.branchheads(branch, closed=True)):
5293 5322 t += _(' (head closed)')
5294 5323 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5295 5324 t += _(' (clean)')
5296 5325 cleanworkdir = True
5297 5326 elif pnode not in bheads:
5298 5327 t += _(' (new branch head)')
5299 5328
5300 5329 if cleanworkdir:
5301 5330 ui.status(_('commit: %s\n') % t.strip())
5302 5331 else:
5303 5332 ui.write(_('commit: %s\n') % t.strip())
5304 5333
5305 5334 # all ancestors of branch heads - all ancestors of parent = new csets
5306 5335 new = [0] * len(repo)
5307 5336 cl = repo.changelog
5308 5337 for a in [cl.rev(n) for n in bheads]:
5309 5338 new[a] = 1
5310 5339 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
5311 5340 new[a] = 1
5312 5341 for a in [p.rev() for p in parents]:
5313 5342 if a >= 0:
5314 5343 new[a] = 0
5315 5344 for a in cl.ancestors(*[p.rev() for p in parents]):
5316 5345 new[a] = 0
5317 5346 new = sum(new)
5318 5347
5319 5348 if new == 0:
5320 5349 ui.status(_('update: (current)\n'))
5321 5350 elif pnode not in bheads:
5322 5351 ui.write(_('update: %d new changesets (update)\n') % new)
5323 5352 else:
5324 5353 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5325 5354 (new, len(bheads)))
5326 5355
5327 5356 if opts.get('remote'):
5328 5357 t = []
5329 5358 source, branches = hg.parseurl(ui.expandpath('default'))
5330 5359 other = hg.peer(repo, {}, source)
5331 5360 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
5332 5361 ui.debug('comparing with %s\n' % util.hidepassword(source))
5333 5362 repo.ui.pushbuffer()
5334 5363 commoninc = discovery.findcommonincoming(repo, other)
5335 5364 _common, incoming, _rheads = commoninc
5336 5365 repo.ui.popbuffer()
5337 5366 if incoming:
5338 5367 t.append(_('1 or more incoming'))
5339 5368
5340 5369 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5341 5370 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5342 5371 if source != dest:
5343 5372 other = hg.peer(repo, {}, dest)
5344 5373 commoninc = None
5345 5374 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5346 5375 repo.ui.pushbuffer()
5347 5376 common, outheads = discovery.findcommonoutgoing(repo, other,
5348 5377 commoninc=commoninc)
5349 5378 repo.ui.popbuffer()
5350 5379 o = repo.changelog.findmissing(common=common, heads=outheads)
5351 5380 if o:
5352 5381 t.append(_('%d outgoing') % len(o))
5353 5382 if 'bookmarks' in other.listkeys('namespaces'):
5354 5383 lmarks = repo.listkeys('bookmarks')
5355 5384 rmarks = other.listkeys('bookmarks')
5356 5385 diff = set(rmarks) - set(lmarks)
5357 5386 if len(diff) > 0:
5358 5387 t.append(_('%d incoming bookmarks') % len(diff))
5359 5388 diff = set(lmarks) - set(rmarks)
5360 5389 if len(diff) > 0:
5361 5390 t.append(_('%d outgoing bookmarks') % len(diff))
5362 5391
5363 5392 if t:
5364 5393 ui.write(_('remote: %s\n') % (', '.join(t)))
5365 5394 else:
5366 5395 ui.status(_('remote: (synced)\n'))
5367 5396
5368 5397 @command('tag',
5369 5398 [('f', 'force', None, _('force tag')),
5370 5399 ('l', 'local', None, _('make the tag local')),
5371 5400 ('r', 'rev', '', _('revision to tag'), _('REV')),
5372 5401 ('', 'remove', None, _('remove a tag')),
5373 5402 # -l/--local is already there, commitopts cannot be used
5374 5403 ('e', 'edit', None, _('edit commit message')),
5375 5404 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5376 5405 ] + commitopts2,
5377 5406 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5378 5407 def tag(ui, repo, name1, *names, **opts):
5379 5408 """add one or more tags for the current or given revision
5380 5409
5381 5410 Name a particular revision using <name>.
5382 5411
5383 5412 Tags are used to name particular revisions of the repository and are
5384 5413 very useful to compare different revisions, to go back to significant
5385 5414 earlier versions or to mark branch points as releases, etc. Changing
5386 5415 an existing tag is normally disallowed; use -f/--force to override.
5387 5416
5388 5417 If no revision is given, the parent of the working directory is
5389 5418 used, or tip if no revision is checked out.
5390 5419
5391 5420 To facilitate version control, distribution, and merging of tags,
5392 5421 they are stored as a file named ".hgtags" which is managed similarly
5393 5422 to other project files and can be hand-edited if necessary. This
5394 5423 also means that tagging creates a new commit. The file
5395 5424 ".hg/localtags" is used for local tags (not shared among
5396 5425 repositories).
5397 5426
5398 5427 Tag commits are usually made at the head of a branch. If the parent
5399 5428 of the working directory is not a branch head, :hg:`tag` aborts; use
5400 5429 -f/--force to force the tag commit to be based on a non-head
5401 5430 changeset.
5402 5431
5403 5432 See :hg:`help dates` for a list of formats valid for -d/--date.
5404 5433
5405 5434 Since tag names have priority over branch names during revision
5406 5435 lookup, using an existing branch name as a tag name is discouraged.
5407 5436
5408 5437 Returns 0 on success.
5409 5438 """
5410 5439
5411 5440 rev_ = "."
5412 5441 names = [t.strip() for t in (name1,) + names]
5413 5442 if len(names) != len(set(names)):
5414 5443 raise util.Abort(_('tag names must be unique'))
5415 5444 for n in names:
5416 5445 if n in ['tip', '.', 'null']:
5417 5446 raise util.Abort(_("the name '%s' is reserved") % n)
5418 5447 if not n:
5419 5448 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
5420 5449 if opts.get('rev') and opts.get('remove'):
5421 5450 raise util.Abort(_("--rev and --remove are incompatible"))
5422 5451 if opts.get('rev'):
5423 5452 rev_ = opts['rev']
5424 5453 message = opts.get('message')
5425 5454 if opts.get('remove'):
5426 5455 expectedtype = opts.get('local') and 'local' or 'global'
5427 5456 for n in names:
5428 5457 if not repo.tagtype(n):
5429 5458 raise util.Abort(_("tag '%s' does not exist") % n)
5430 5459 if repo.tagtype(n) != expectedtype:
5431 5460 if expectedtype == 'global':
5432 5461 raise util.Abort(_("tag '%s' is not a global tag") % n)
5433 5462 else:
5434 5463 raise util.Abort(_("tag '%s' is not a local tag") % n)
5435 5464 rev_ = nullid
5436 5465 if not message:
5437 5466 # we don't translate commit messages
5438 5467 message = 'Removed tag %s' % ', '.join(names)
5439 5468 elif not opts.get('force'):
5440 5469 for n in names:
5441 5470 if n in repo.tags():
5442 5471 raise util.Abort(_("tag '%s' already exists "
5443 5472 "(use -f to force)") % n)
5444 5473 if not opts.get('local'):
5445 5474 p1, p2 = repo.dirstate.parents()
5446 5475 if p2 != nullid:
5447 5476 raise util.Abort(_('uncommitted merge'))
5448 5477 bheads = repo.branchheads()
5449 5478 if not opts.get('force') and bheads and p1 not in bheads:
5450 5479 raise util.Abort(_('not at a branch head (use -f to force)'))
5451 5480 r = scmutil.revsingle(repo, rev_).node()
5452 5481
5453 5482 if not message:
5454 5483 # we don't translate commit messages
5455 5484 message = ('Added tag %s for changeset %s' %
5456 5485 (', '.join(names), short(r)))
5457 5486
5458 5487 date = opts.get('date')
5459 5488 if date:
5460 5489 date = util.parsedate(date)
5461 5490
5462 5491 if opts.get('edit'):
5463 5492 message = ui.edit(message, ui.username())
5464 5493
5465 5494 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5466 5495
5467 5496 @command('tags', [], '')
5468 5497 def tags(ui, repo):
5469 5498 """list repository tags
5470 5499
5471 5500 This lists both regular and local tags. When the -v/--verbose
5472 5501 switch is used, a third column "local" is printed for local tags.
5473 5502
5474 5503 Returns 0 on success.
5475 5504 """
5476 5505
5477 5506 hexfunc = ui.debugflag and hex or short
5478 5507 tagtype = ""
5479 5508
5480 5509 for t, n in reversed(repo.tagslist()):
5481 5510 if ui.quiet:
5482 5511 ui.write("%s\n" % t, label='tags.normal')
5483 5512 continue
5484 5513
5485 5514 hn = hexfunc(n)
5486 5515 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5487 5516 rev = ui.label(r, 'log.changeset')
5488 5517 spaces = " " * (30 - encoding.colwidth(t))
5489 5518
5490 5519 tag = ui.label(t, 'tags.normal')
5491 5520 if ui.verbose:
5492 5521 if repo.tagtype(t) == 'local':
5493 5522 tagtype = " local"
5494 5523 tag = ui.label(t, 'tags.local')
5495 5524 else:
5496 5525 tagtype = ""
5497 5526 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5498 5527
5499 5528 @command('tip',
5500 5529 [('p', 'patch', None, _('show patch')),
5501 5530 ('g', 'git', None, _('use git extended diff format')),
5502 5531 ] + templateopts,
5503 5532 _('[-p] [-g]'))
5504 5533 def tip(ui, repo, **opts):
5505 5534 """show the tip revision
5506 5535
5507 5536 The tip revision (usually just called the tip) is the changeset
5508 5537 most recently added to the repository (and therefore the most
5509 5538 recently changed head).
5510 5539
5511 5540 If you have just made a commit, that commit will be the tip. If
5512 5541 you have just pulled changes from another repository, the tip of
5513 5542 that repository becomes the current tip. The "tip" tag is special
5514 5543 and cannot be renamed or assigned to a different changeset.
5515 5544
5516 5545 Returns 0 on success.
5517 5546 """
5518 5547 displayer = cmdutil.show_changeset(ui, repo, opts)
5519 5548 displayer.show(repo[len(repo) - 1])
5520 5549 displayer.close()
5521 5550
5522 5551 @command('unbundle',
5523 5552 [('u', 'update', None,
5524 5553 _('update to new branch head if changesets were unbundled'))],
5525 5554 _('[-u] FILE...'))
5526 5555 def unbundle(ui, repo, fname1, *fnames, **opts):
5527 5556 """apply one or more changegroup files
5528 5557
5529 5558 Apply one or more compressed changegroup files generated by the
5530 5559 bundle command.
5531 5560
5532 5561 Returns 0 on success, 1 if an update has unresolved files.
5533 5562 """
5534 5563 fnames = (fname1,) + fnames
5535 5564
5536 5565 lock = repo.lock()
5537 5566 wc = repo['.']
5538 5567 try:
5539 5568 for fname in fnames:
5540 5569 f = url.open(ui, fname)
5541 5570 gen = changegroup.readbundle(f, fname)
5542 5571 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
5543 5572 lock=lock)
5544 5573 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5545 5574 finally:
5546 5575 lock.release()
5547 5576 return postincoming(ui, repo, modheads, opts.get('update'), None)
5548 5577
5549 5578 @command('^update|up|checkout|co',
5550 5579 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5551 5580 ('c', 'check', None,
5552 5581 _('update across branches if no uncommitted changes')),
5553 5582 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5554 5583 ('r', 'rev', '', _('revision'), _('REV'))],
5555 5584 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5556 5585 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5557 5586 """update working directory (or switch revisions)
5558 5587
5559 5588 Update the repository's working directory to the specified
5560 5589 changeset. If no changeset is specified, update to the tip of the
5561 5590 current named branch.
5562 5591
5563 5592 If the changeset is not a descendant of the working directory's
5564 5593 parent, the update is aborted. With the -c/--check option, the
5565 5594 working directory is checked for uncommitted changes; if none are
5566 5595 found, the working directory is updated to the specified
5567 5596 changeset.
5568 5597
5569 5598 Update sets the working directory's parent revison to the specified
5570 5599 changeset (see :hg:`help parents`).
5571 5600
5572 5601 The following rules apply when the working directory contains
5573 5602 uncommitted changes:
5574 5603
5575 5604 1. If neither -c/--check nor -C/--clean is specified, and if
5576 5605 the requested changeset is an ancestor or descendant of
5577 5606 the working directory's parent, the uncommitted changes
5578 5607 are merged into the requested changeset and the merged
5579 5608 result is left uncommitted. If the requested changeset is
5580 5609 not an ancestor or descendant (that is, it is on another
5581 5610 branch), the update is aborted and the uncommitted changes
5582 5611 are preserved.
5583 5612
5584 5613 2. With the -c/--check option, the update is aborted and the
5585 5614 uncommitted changes are preserved.
5586 5615
5587 5616 3. With the -C/--clean option, uncommitted changes are discarded and
5588 5617 the working directory is updated to the requested changeset.
5589 5618
5590 5619 Use null as the changeset to remove the working directory (like
5591 5620 :hg:`clone -U`).
5592 5621
5593 5622 If you want to revert just one file to an older revision, use
5594 5623 :hg:`revert [-r REV] NAME`.
5595 5624
5596 5625 See :hg:`help dates` for a list of formats valid for -d/--date.
5597 5626
5598 5627 Returns 0 on success, 1 if there are unresolved files.
5599 5628 """
5600 5629 if rev and node:
5601 5630 raise util.Abort(_("please specify just one revision"))
5602 5631
5603 5632 if rev is None or rev == '':
5604 5633 rev = node
5605 5634
5606 5635 # if we defined a bookmark, we have to remember the original bookmark name
5607 5636 brev = rev
5608 5637 rev = scmutil.revsingle(repo, rev, rev).rev()
5609 5638
5610 5639 if check and clean:
5611 5640 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5612 5641
5613 5642 if check:
5614 5643 # we could use dirty() but we can ignore merge and branch trivia
5615 5644 c = repo[None]
5616 5645 if c.modified() or c.added() or c.removed():
5617 5646 raise util.Abort(_("uncommitted local changes"))
5618 5647
5619 5648 if date:
5620 5649 if rev is not None:
5621 5650 raise util.Abort(_("you can't specify a revision and a date"))
5622 5651 rev = cmdutil.finddate(ui, repo, date)
5623 5652
5624 5653 if clean or check:
5625 5654 ret = hg.clean(repo, rev)
5626 5655 else:
5627 5656 ret = hg.update(repo, rev)
5628 5657
5629 5658 if brev in repo._bookmarks:
5630 5659 bookmarks.setcurrent(repo, brev)
5631 5660
5632 5661 return ret
5633 5662
5634 5663 @command('verify', [])
5635 5664 def verify(ui, repo):
5636 5665 """verify the integrity of the repository
5637 5666
5638 5667 Verify the integrity of the current repository.
5639 5668
5640 5669 This will perform an extensive check of the repository's
5641 5670 integrity, validating the hashes and checksums of each entry in
5642 5671 the changelog, manifest, and tracked files, as well as the
5643 5672 integrity of their crosslinks and indices.
5644 5673
5645 5674 Returns 0 on success, 1 if errors are encountered.
5646 5675 """
5647 5676 return hg.verify(repo)
5648 5677
5649 5678 @command('version', [])
5650 5679 def version_(ui):
5651 5680 """output version and copyright information"""
5652 5681 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5653 5682 % util.version())
5654 5683 ui.status(_(
5655 5684 "(see http://mercurial.selenic.com for more information)\n"
5656 5685 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5657 5686 "This is free software; see the source for copying conditions. "
5658 5687 "There is NO\nwarranty; "
5659 5688 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5660 5689 ))
5661 5690
5662 5691 norepo = ("clone init version help debugcommands debugcomplete"
5663 5692 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5664 5693 " debugknown debuggetbundle debugbundle")
5665 5694 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5666 5695 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,202 +1,202 b''
1 1 # discovery.py - protocol changeset discovery functions
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, short
9 9 from i18n import _
10 10 import util, setdiscovery, treediscovery
11 11
12 12 def findcommonincoming(repo, remote, heads=None, force=False):
13 13 """Return a tuple (common, anyincoming, heads) used to identify the common
14 14 subset of nodes between repo and remote.
15 15
16 16 "common" is a list of (at least) the heads of the common subset.
17 17 "anyincoming" is testable as a boolean indicating if any nodes are missing
18 18 locally. If remote does not support getbundle, this actually is a list of
19 19 roots of the nodes that would be incoming, to be supplied to
20 20 changegroupsubset. No code except for pull should be relying on this fact
21 21 any longer.
22 22 "heads" is either the supplied heads, or else the remote's heads.
23 23
24 24 If you pass heads and they are all known locally, the reponse lists justs
25 25 these heads in "common" and in "heads".
26 26
27 27 Please use findcommonoutgoing to compute the set of outgoing nodes to give
28 28 extensions a good hook into outgoing.
29 29 """
30 30
31 31 if not remote.capable('getbundle'):
32 32 return treediscovery.findcommonincoming(repo, remote, heads, force)
33 33
34 34 if heads:
35 35 allknown = True
36 36 nm = repo.changelog.nodemap
37 37 for h in heads:
38 38 if nm.get(h) is None:
39 39 allknown = False
40 40 break
41 41 if allknown:
42 42 return (heads, False, heads)
43 43
44 44 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
45 45 abortwhenunrelated=not force)
46 46 common, anyinc, srvheads = res
47 47 return (list(common), anyinc, heads or list(srvheads))
48 48
49 49 def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None):
50 50 '''Return a tuple (common, anyoutgoing, heads) used to identify the set
51 51 of nodes present in repo but not in other.
52 52
53 53 If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive)
54 54 are included. If you already know the local repo's heads, passing them in
55 55 onlyheads is faster than letting them be recomputed here.
56 56
57 57 If commoninc is given, it must the the result of a prior call to
58 58 findcommonincoming(repo, other, force) to avoid recomputing it here.
59 59
60 60 The returned tuple is meant to be passed to changelog.findmissing.'''
61 61 common, _any, _hds = commoninc or findcommonincoming(repo, other, force=force)
62 62 return (common, onlyheads or repo.heads())
63 63
64 64 def prepush(repo, remote, force, revs, newbranch):
65 65 '''Analyze the local and remote repositories and determine which
66 66 changesets need to be pushed to the remote. Return value depends
67 67 on circumstances:
68 68
69 69 If we are not going to push anything, return a tuple (None,
70 70 outgoing, common) where outgoing is 0 if there are no outgoing
71 71 changesets and 1 if there are, but we refuse to push them
72 72 (e.g. would create new remote heads). The third element "common"
73 73 is the list of heads of the common set between local and remote.
74 74
75 75 Otherwise, return a tuple (changegroup, remoteheads, futureheads),
76 76 where changegroup is a readable file-like object whose read()
77 77 returns successive changegroup chunks ready to be sent over the
78 78 wire, remoteheads is the list of remote heads and futureheads is
79 79 the list of heads of the common set between local and remote to
80 80 be after push completion.
81 81 '''
82 82 commoninc = findcommonincoming(repo, remote, force=force)
83 83 common, revs = findcommonoutgoing(repo, remote, onlyheads=revs,
84 84 commoninc=commoninc, force=force)
85 85 _common, inc, remoteheads = commoninc
86 86
87 87 cl = repo.changelog
88 88 outg = cl.findmissing(common, revs)
89 89
90 90 if not outg:
91 91 repo.ui.status(_("no changes found\n"))
92 92 return None, 1, common
93 93
94 94 if not force and remoteheads != [nullid]:
95 95 if remote.capable('branchmap'):
96 96 # Check for each named branch if we're creating new remote heads.
97 97 # To be a remote head after push, node must be either:
98 98 # - unknown locally
99 99 # - a local outgoing head descended from update
100 100 # - a remote head that's known locally and not
101 101 # ancestral to an outgoing head
102 102
103 103 # 1. Create set of branches involved in the push.
104 104 branches = set(repo[n].branch() for n in outg)
105 105
106 106 # 2. Check for new branches on the remote.
107 107 remotemap = remote.branchmap()
108 108 newbranches = branches - set(remotemap)
109 109 if newbranches and not newbranch: # new branch requires --new-branch
110 110 branchnames = ', '.join(sorted(newbranches))
111 111 raise util.Abort(_("push creates new remote branches: %s!")
112 112 % branchnames,
113 113 hint=_("use 'hg push --new-branch' to create"
114 114 " new remote branches"))
115 115 branches.difference_update(newbranches)
116 116
117 117 # 3. Construct the initial oldmap and newmap dicts.
118 118 # They contain information about the remote heads before and
119 119 # after the push, respectively.
120 120 # Heads not found locally are not included in either dict,
121 121 # since they won't be affected by the push.
122 122 # unsynced contains all branches with incoming changesets.
123 123 oldmap = {}
124 124 newmap = {}
125 125 unsynced = set()
126 126 for branch in branches:
127 127 remotebrheads = remotemap[branch]
128 128 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
129 129 oldmap[branch] = prunedbrheads
130 130 newmap[branch] = list(prunedbrheads)
131 131 if len(remotebrheads) > len(prunedbrheads):
132 132 unsynced.add(branch)
133 133
134 134 # 4. Update newmap with outgoing changes.
135 135 # This will possibly add new heads and remove existing ones.
136 136 ctxgen = (repo[n] for n in outg)
137 137 repo._updatebranchcache(newmap, ctxgen)
138 138
139 139 else:
140 140 # 1-4b. old servers: Check for new topological heads.
141 141 # Construct {old,new}map with branch = None (topological branch).
142 142 # (code based on _updatebranchcache)
143 143 oldheads = set(h for h in remoteheads if h in cl.nodemap)
144 144 newheads = oldheads.union(outg)
145 145 if len(newheads) > 1:
146 146 for latest in reversed(outg):
147 147 if latest not in newheads:
148 148 continue
149 149 minhrev = min(cl.rev(h) for h in newheads)
150 150 reachable = cl.reachable(latest, cl.node(minhrev))
151 151 reachable.remove(latest)
152 152 newheads.difference_update(reachable)
153 153 branches = set([None])
154 154 newmap = {None: newheads}
155 155 oldmap = {None: oldheads}
156 156 unsynced = inc and branches or set()
157 157
158 158 # 5. Check for new heads.
159 159 # If there are more heads after the push than before, a suitable
160 160 # error message, depending on unsynced status, is displayed.
161 161 error = None
162 162 for branch in branches:
163 163 newhs = set(newmap[branch])
164 164 oldhs = set(oldmap[branch])
165 165 if len(newhs) > len(oldhs):
166 166 dhs = list(newhs - oldhs)
167 167 if error is None:
168 168 if branch not in ('default', None):
169 169 error = _("push creates new remote head %s "
170 170 "on branch '%s'!") % (short(dhs[0]), branch)
171 171 else:
172 172 error = _("push creates new remote head %s!"
173 173 ) % short(dhs[0])
174 174 if branch in unsynced:
175 175 hint = _("you should pull and merge or "
176 176 "use push -f to force")
177 177 else:
178 178 hint = _("did you forget to merge? "
179 179 "use push -f to force")
180 180 if branch is not None:
181 repo.ui.note("new remote heads on branch '%s'\n" % branch)
181 repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
182 182 for h in dhs:
183 repo.ui.note("new remote head %s\n" % short(h))
183 repo.ui.note(_("new remote head %s\n") % short(h))
184 184 if error:
185 185 raise util.Abort(error, hint=hint)
186 186
187 187 # 6. Check for unsynced changes on involved branches.
188 188 if unsynced:
189 189 repo.ui.warn(_("note: unsynced remote changes!\n"))
190 190
191 191 if revs is None:
192 192 # use the fast path, no race possible on push
193 193 cg = repo._changegroup(outg, 'push')
194 194 else:
195 195 cg = repo.getbundle('push', heads=revs, common=common)
196 196 # no need to compute outg ancestor. All node in outg have either:
197 197 # - parents in outg
198 198 # - parents in common
199 199 # - nullid parent
200 200 rset = repo.set('heads(%ln + %ln)', common, outg)
201 201 futureheads = [ctx.node() for ctx in rset]
202 202 return cg, remoteheads, futureheads
@@ -1,738 +1,738 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as uimod
13 13
14 14 class request(object):
15 15 def __init__(self, args, ui=None, repo=None, fin=None, fout=None, ferr=None):
16 16 self.args = args
17 17 self.ui = ui
18 18 self.repo = repo
19 19
20 20 # input/output/error streams
21 21 self.fin = fin
22 22 self.fout = fout
23 23 self.ferr = ferr
24 24
25 25 def run():
26 26 "run the command in sys.argv"
27 27 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
28 28
29 29 def dispatch(req):
30 30 "run the command specified in req.args"
31 31 if req.ferr:
32 32 ferr = req.ferr
33 33 elif req.ui:
34 34 ferr = req.ui.ferr
35 35 else:
36 36 ferr = sys.stderr
37 37
38 38 try:
39 39 if not req.ui:
40 40 req.ui = uimod.ui()
41 41 if '--traceback' in req.args:
42 42 req.ui.setconfig('ui', 'traceback', 'on')
43 43
44 44 # set ui streams from the request
45 45 if req.fin:
46 46 req.ui.fin = req.fin
47 47 if req.fout:
48 48 req.ui.fout = req.fout
49 49 if req.ferr:
50 50 req.ui.ferr = req.ferr
51 51 except util.Abort, inst:
52 52 ferr.write(_("abort: %s\n") % inst)
53 53 if inst.hint:
54 54 ferr.write(_("(%s)\n") % inst.hint)
55 55 return -1
56 56 except error.ParseError, inst:
57 57 if len(inst.args) > 1:
58 58 ferr.write(_("hg: parse error at %s: %s\n") %
59 59 (inst.args[1], inst.args[0]))
60 60 else:
61 61 ferr.write(_("hg: parse error: %s\n") % inst.args[0])
62 62 return -1
63 63
64 64 return _runcatch(req)
65 65
66 66 def _runcatch(req):
67 67 def catchterm(*args):
68 68 raise error.SignalInterrupt
69 69
70 70 ui = req.ui
71 71 try:
72 72 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
73 73 num = getattr(signal, name, None)
74 74 if num:
75 75 signal.signal(num, catchterm)
76 76 except ValueError:
77 77 pass # happens if called in a thread
78 78
79 79 try:
80 80 try:
81 81 # enter the debugger before command execution
82 82 if '--debugger' in req.args:
83 83 ui.warn(_("entering debugger - "
84 84 "type c to continue starting hg or h for help\n"))
85 85 pdb.set_trace()
86 86 try:
87 87 return _dispatch(req)
88 88 finally:
89 89 ui.flush()
90 90 except:
91 91 # enter the debugger when we hit an exception
92 92 if '--debugger' in req.args:
93 93 traceback.print_exc()
94 94 pdb.post_mortem(sys.exc_info()[2])
95 95 ui.traceback()
96 96 raise
97 97
98 98 # Global exception handling, alphabetically
99 99 # Mercurial-specific first, followed by built-in and library exceptions
100 100 except error.AmbiguousCommand, inst:
101 101 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
102 102 (inst.args[0], " ".join(inst.args[1])))
103 103 except error.ParseError, inst:
104 104 if len(inst.args) > 1:
105 105 ui.warn(_("hg: parse error at %s: %s\n") %
106 106 (inst.args[1], inst.args[0]))
107 107 else:
108 108 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
109 109 return -1
110 110 except error.LockHeld, inst:
111 111 if inst.errno == errno.ETIMEDOUT:
112 112 reason = _('timed out waiting for lock held by %s') % inst.locker
113 113 else:
114 114 reason = _('lock held by %s') % inst.locker
115 115 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
116 116 except error.LockUnavailable, inst:
117 117 ui.warn(_("abort: could not lock %s: %s\n") %
118 118 (inst.desc or inst.filename, inst.strerror))
119 119 except error.CommandError, inst:
120 120 if inst.args[0]:
121 121 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
122 122 commands.help_(ui, inst.args[0], full=False, command=True)
123 123 else:
124 124 ui.warn(_("hg: %s\n") % inst.args[1])
125 125 commands.help_(ui, 'shortlist')
126 126 except error.OutOfBandError, inst:
127 ui.warn("abort: remote error:\n")
127 ui.warn(_("abort: remote error:\n"))
128 128 ui.warn(''.join(inst.args))
129 129 except error.RepoError, inst:
130 130 ui.warn(_("abort: %s!\n") % inst)
131 131 if inst.hint:
132 132 ui.warn(_("(%s)\n") % inst.hint)
133 133 except error.ResponseError, inst:
134 134 ui.warn(_("abort: %s") % inst.args[0])
135 135 if not isinstance(inst.args[1], basestring):
136 136 ui.warn(" %r\n" % (inst.args[1],))
137 137 elif not inst.args[1]:
138 138 ui.warn(_(" empty string\n"))
139 139 else:
140 140 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
141 141 except error.RevlogError, inst:
142 142 ui.warn(_("abort: %s!\n") % inst)
143 143 except error.SignalInterrupt:
144 144 ui.warn(_("killed!\n"))
145 145 except error.UnknownCommand, inst:
146 146 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
147 147 try:
148 148 # check if the command is in a disabled extension
149 149 # (but don't check for extensions themselves)
150 150 commands.help_(ui, inst.args[0], unknowncmd=True)
151 151 except error.UnknownCommand:
152 152 commands.help_(ui, 'shortlist')
153 153 except util.Abort, inst:
154 154 ui.warn(_("abort: %s\n") % inst)
155 155 if inst.hint:
156 156 ui.warn(_("(%s)\n") % inst.hint)
157 157 except ImportError, inst:
158 158 ui.warn(_("abort: %s!\n") % inst)
159 159 m = str(inst).split()[-1]
160 160 if m in "mpatch bdiff".split():
161 161 ui.warn(_("(did you forget to compile extensions?)\n"))
162 162 elif m in "zlib".split():
163 163 ui.warn(_("(is your Python install correct?)\n"))
164 164 except IOError, inst:
165 165 if util.safehasattr(inst, "code"):
166 166 ui.warn(_("abort: %s\n") % inst)
167 167 elif util.safehasattr(inst, "reason"):
168 168 try: # usually it is in the form (errno, strerror)
169 169 reason = inst.reason.args[1]
170 170 except (AttributeError, IndexError):
171 171 # it might be anything, for example a string
172 172 reason = inst.reason
173 173 ui.warn(_("abort: error: %s\n") % reason)
174 174 elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE:
175 175 if ui.debugflag:
176 176 ui.warn(_("broken pipe\n"))
177 177 elif getattr(inst, "strerror", None):
178 178 if getattr(inst, "filename", None):
179 179 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
180 180 else:
181 181 ui.warn(_("abort: %s\n") % inst.strerror)
182 182 else:
183 183 raise
184 184 except OSError, inst:
185 185 if getattr(inst, "filename", None):
186 186 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
187 187 else:
188 188 ui.warn(_("abort: %s\n") % inst.strerror)
189 189 except KeyboardInterrupt:
190 190 try:
191 191 ui.warn(_("interrupted!\n"))
192 192 except IOError, inst:
193 193 if inst.errno == errno.EPIPE:
194 194 if ui.debugflag:
195 195 ui.warn(_("\nbroken pipe\n"))
196 196 else:
197 197 raise
198 198 except MemoryError:
199 199 ui.warn(_("abort: out of memory\n"))
200 200 except SystemExit, inst:
201 201 # Commands shouldn't sys.exit directly, but give a return code.
202 202 # Just in case catch this and and pass exit code to caller.
203 203 return inst.code
204 204 except socket.error, inst:
205 205 ui.warn(_("abort: %s\n") % inst.args[-1])
206 206 except:
207 207 ui.warn(_("** unknown exception encountered,"
208 208 " please report by visiting\n"))
209 209 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
210 210 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
211 211 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
212 212 % util.version())
213 213 ui.warn(_("** Extensions loaded: %s\n")
214 214 % ", ".join([x[0] for x in extensions.extensions()]))
215 215 raise
216 216
217 217 return -1
218 218
219 219 def aliasargs(fn, givenargs):
220 220 args = getattr(fn, 'args', [])
221 221 if args and givenargs:
222 222 cmd = ' '.join(map(util.shellquote, args))
223 223
224 224 nums = []
225 225 def replacer(m):
226 226 num = int(m.group(1)) - 1
227 227 nums.append(num)
228 228 return givenargs[num]
229 229 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
230 230 givenargs = [x for i, x in enumerate(givenargs)
231 231 if i not in nums]
232 232 args = shlex.split(cmd)
233 233 return args + givenargs
234 234
235 235 class cmdalias(object):
236 236 def __init__(self, name, definition, cmdtable):
237 237 self.name = self.cmd = name
238 238 self.cmdname = ''
239 239 self.definition = definition
240 240 self.args = []
241 241 self.opts = []
242 242 self.help = ''
243 243 self.norepo = True
244 244 self.badalias = False
245 245
246 246 try:
247 247 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
248 248 for alias, e in cmdtable.iteritems():
249 249 if e is entry:
250 250 self.cmd = alias
251 251 break
252 252 self.shadows = True
253 253 except error.UnknownCommand:
254 254 self.shadows = False
255 255
256 256 if not self.definition:
257 257 def fn(ui, *args):
258 258 ui.warn(_("no definition for alias '%s'\n") % self.name)
259 259 return 1
260 260 self.fn = fn
261 261 self.badalias = True
262 262
263 263 return
264 264
265 265 if self.definition.startswith('!'):
266 266 self.shell = True
267 267 def fn(ui, *args):
268 268 env = {'HG_ARGS': ' '.join((self.name,) + args)}
269 269 def _checkvar(m):
270 270 if m.groups()[0] == '$':
271 271 return m.group()
272 272 elif int(m.groups()[0]) <= len(args):
273 273 return m.group()
274 274 else:
275 275 ui.debug("No argument found for substitution "
276 276 "of %i variable in alias '%s' definition."
277 277 % (int(m.groups()[0]), self.name))
278 278 return ''
279 279 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
280 280 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
281 281 replace['0'] = self.name
282 282 replace['@'] = ' '.join(args)
283 283 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
284 284 return util.system(cmd, environ=env, out=ui.fout)
285 285 self.fn = fn
286 286 return
287 287
288 288 args = shlex.split(self.definition)
289 289 self.cmdname = cmd = args.pop(0)
290 290 args = map(util.expandpath, args)
291 291
292 292 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
293 293 if _earlygetopt([invalidarg], args):
294 294 def fn(ui, *args):
295 295 ui.warn(_("error in definition for alias '%s': %s may only "
296 296 "be given on the command line\n")
297 297 % (self.name, invalidarg))
298 298 return 1
299 299
300 300 self.fn = fn
301 301 self.badalias = True
302 302 return
303 303
304 304 try:
305 305 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
306 306 if len(tableentry) > 2:
307 307 self.fn, self.opts, self.help = tableentry
308 308 else:
309 309 self.fn, self.opts = tableentry
310 310
311 311 self.args = aliasargs(self.fn, args)
312 312 if cmd not in commands.norepo.split(' '):
313 313 self.norepo = False
314 314 if self.help.startswith("hg " + cmd):
315 315 # drop prefix in old-style help lines so hg shows the alias
316 316 self.help = self.help[4 + len(cmd):]
317 317 self.__doc__ = self.fn.__doc__
318 318
319 319 except error.UnknownCommand:
320 320 def fn(ui, *args):
321 321 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
322 322 % (self.name, cmd))
323 323 try:
324 324 # check if the command is in a disabled extension
325 325 commands.help_(ui, cmd, unknowncmd=True)
326 326 except error.UnknownCommand:
327 327 pass
328 328 return 1
329 329 self.fn = fn
330 330 self.badalias = True
331 331 except error.AmbiguousCommand:
332 332 def fn(ui, *args):
333 333 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
334 334 % (self.name, cmd))
335 335 return 1
336 336 self.fn = fn
337 337 self.badalias = True
338 338
339 339 def __call__(self, ui, *args, **opts):
340 340 if self.shadows:
341 341 ui.debug("alias '%s' shadows command '%s'\n" %
342 342 (self.name, self.cmdname))
343 343
344 344 if util.safehasattr(self, 'shell'):
345 345 return self.fn(ui, *args, **opts)
346 346 else:
347 347 try:
348 348 util.checksignature(self.fn)(ui, *args, **opts)
349 349 except error.SignatureError:
350 350 args = ' '.join([self.cmdname] + self.args)
351 351 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
352 352 raise
353 353
354 354 def addaliases(ui, cmdtable):
355 355 # aliases are processed after extensions have been loaded, so they
356 356 # may use extension commands. Aliases can also use other alias definitions,
357 357 # but only if they have been defined prior to the current definition.
358 358 for alias, definition in ui.configitems('alias'):
359 359 aliasdef = cmdalias(alias, definition, cmdtable)
360 360
361 361 try:
362 362 olddef = cmdtable[aliasdef.cmd][0]
363 363 if olddef.definition == aliasdef.definition:
364 364 continue
365 365 except (KeyError, AttributeError):
366 366 # definition might not exist or it might not be a cmdalias
367 367 pass
368 368
369 369 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
370 370 if aliasdef.norepo:
371 371 commands.norepo += ' %s' % alias
372 372
373 373 def _parse(ui, args):
374 374 options = {}
375 375 cmdoptions = {}
376 376
377 377 try:
378 378 args = fancyopts.fancyopts(args, commands.globalopts, options)
379 379 except fancyopts.getopt.GetoptError, inst:
380 380 raise error.CommandError(None, inst)
381 381
382 382 if args:
383 383 cmd, args = args[0], args[1:]
384 384 aliases, entry = cmdutil.findcmd(cmd, commands.table,
385 385 ui.config("ui", "strict"))
386 386 cmd = aliases[0]
387 387 args = aliasargs(entry[0], args)
388 388 defaults = ui.config("defaults", cmd)
389 389 if defaults:
390 390 args = map(util.expandpath, shlex.split(defaults)) + args
391 391 c = list(entry[1])
392 392 else:
393 393 cmd = None
394 394 c = []
395 395
396 396 # combine global options into local
397 397 for o in commands.globalopts:
398 398 c.append((o[0], o[1], options[o[1]], o[3]))
399 399
400 400 try:
401 401 args = fancyopts.fancyopts(args, c, cmdoptions, True)
402 402 except fancyopts.getopt.GetoptError, inst:
403 403 raise error.CommandError(cmd, inst)
404 404
405 405 # separate global options back out
406 406 for o in commands.globalopts:
407 407 n = o[1]
408 408 options[n] = cmdoptions[n]
409 409 del cmdoptions[n]
410 410
411 411 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
412 412
413 413 def _parseconfig(ui, config):
414 414 """parse the --config options from the command line"""
415 415 configs = []
416 416
417 417 for cfg in config:
418 418 try:
419 419 name, value = cfg.split('=', 1)
420 420 section, name = name.split('.', 1)
421 421 if not section or not name:
422 422 raise IndexError
423 423 ui.setconfig(section, name, value)
424 424 configs.append((section, name, value))
425 425 except (IndexError, ValueError):
426 426 raise util.Abort(_('malformed --config option: %r '
427 427 '(use --config section.name=value)') % cfg)
428 428
429 429 return configs
430 430
431 431 def _earlygetopt(aliases, args):
432 432 """Return list of values for an option (or aliases).
433 433
434 434 The values are listed in the order they appear in args.
435 435 The options and values are removed from args.
436 436 """
437 437 try:
438 438 argcount = args.index("--")
439 439 except ValueError:
440 440 argcount = len(args)
441 441 shortopts = [opt for opt in aliases if len(opt) == 2]
442 442 values = []
443 443 pos = 0
444 444 while pos < argcount:
445 445 if args[pos] in aliases:
446 446 if pos + 1 >= argcount:
447 447 # ignore and let getopt report an error if there is no value
448 448 break
449 449 del args[pos]
450 450 values.append(args.pop(pos))
451 451 argcount -= 2
452 452 elif args[pos][:2] in shortopts:
453 453 # short option can have no following space, e.g. hg log -Rfoo
454 454 values.append(args.pop(pos)[2:])
455 455 argcount -= 1
456 456 else:
457 457 pos += 1
458 458 return values
459 459
460 460 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
461 461 # run pre-hook, and abort if it fails
462 462 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
463 463 pats=cmdpats, opts=cmdoptions)
464 464 if ret:
465 465 return ret
466 466 ret = _runcommand(ui, options, cmd, d)
467 467 # run post-hook, passing command result
468 468 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
469 469 result=ret, pats=cmdpats, opts=cmdoptions)
470 470 return ret
471 471
472 472 def _getlocal(ui, rpath):
473 473 """Return (path, local ui object) for the given target path.
474 474
475 475 Takes paths in [cwd]/.hg/hgrc into account."
476 476 """
477 477 try:
478 478 wd = os.getcwd()
479 479 except OSError, e:
480 480 raise util.Abort(_("error getting current working directory: %s") %
481 481 e.strerror)
482 482 path = cmdutil.findrepo(wd) or ""
483 483 if not path:
484 484 lui = ui
485 485 else:
486 486 lui = ui.copy()
487 487 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
488 488
489 489 if rpath and rpath[-1]:
490 490 path = lui.expandpath(rpath[-1])
491 491 lui = ui.copy()
492 492 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
493 493
494 494 return path, lui
495 495
496 496 def _checkshellalias(lui, ui, args):
497 497 norepo = commands.norepo
498 498 options = {}
499 499
500 500 try:
501 501 args = fancyopts.fancyopts(args, commands.globalopts, options)
502 502 except fancyopts.getopt.GetoptError:
503 503 return
504 504
505 505 if not args:
506 506 return
507 507
508 508 cmdtable = commands.table.copy()
509 509 addaliases(lui, cmdtable)
510 510
511 511 cmd = args[0]
512 512 try:
513 513 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
514 514 except (error.AmbiguousCommand, error.UnknownCommand):
515 515 commands.norepo = norepo
516 516 return
517 517
518 518 cmd = aliases[0]
519 519 fn = entry[0]
520 520
521 521 if cmd and util.safehasattr(fn, 'shell'):
522 522 d = lambda: fn(ui, *args[1:])
523 523 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
524 524
525 525 commands.norepo = norepo
526 526
527 527 _loaded = set()
528 528 def _dispatch(req):
529 529 args = req.args
530 530 ui = req.ui
531 531
532 532 # read --config before doing anything else
533 533 # (e.g. to change trust settings for reading .hg/hgrc)
534 534 cfgs = _parseconfig(ui, _earlygetopt(['--config'], args))
535 535
536 536 # check for cwd
537 537 cwd = _earlygetopt(['--cwd'], args)
538 538 if cwd:
539 539 os.chdir(cwd[-1])
540 540
541 541 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
542 542 path, lui = _getlocal(ui, rpath)
543 543
544 544 # Now that we're operating in the right directory/repository with
545 545 # the right config settings, check for shell aliases
546 546 shellaliasfn = _checkshellalias(lui, ui, args)
547 547 if shellaliasfn:
548 548 return shellaliasfn()
549 549
550 550 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
551 551 # reposetup. Programs like TortoiseHg will call _dispatch several
552 552 # times so we keep track of configured extensions in _loaded.
553 553 extensions.loadall(lui)
554 554 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
555 555 # Propagate any changes to lui.__class__ by extensions
556 556 ui.__class__ = lui.__class__
557 557
558 558 # (uisetup and extsetup are handled in extensions.loadall)
559 559
560 560 for name, module in exts:
561 561 cmdtable = getattr(module, 'cmdtable', {})
562 562 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
563 563 if overrides:
564 564 ui.warn(_("extension '%s' overrides commands: %s\n")
565 565 % (name, " ".join(overrides)))
566 566 commands.table.update(cmdtable)
567 567 _loaded.add(name)
568 568
569 569 # (reposetup is handled in hg.repository)
570 570
571 571 addaliases(lui, commands.table)
572 572
573 573 # check for fallback encoding
574 574 fallback = lui.config('ui', 'fallbackencoding')
575 575 if fallback:
576 576 encoding.fallbackencoding = fallback
577 577
578 578 fullargs = args
579 579 cmd, func, args, options, cmdoptions = _parse(lui, args)
580 580
581 581 if options["config"]:
582 582 raise util.Abort(_("option --config may not be abbreviated!"))
583 583 if options["cwd"]:
584 584 raise util.Abort(_("option --cwd may not be abbreviated!"))
585 585 if options["repository"]:
586 586 raise util.Abort(_(
587 587 "Option -R has to be separated from other options (e.g. not -qR) "
588 588 "and --repository may only be abbreviated as --repo!"))
589 589
590 590 if options["encoding"]:
591 591 encoding.encoding = options["encoding"]
592 592 if options["encodingmode"]:
593 593 encoding.encodingmode = options["encodingmode"]
594 594 if options["time"]:
595 595 def get_times():
596 596 t = os.times()
597 597 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
598 598 t = (t[0], t[1], t[2], t[3], time.clock())
599 599 return t
600 600 s = get_times()
601 601 def print_time():
602 602 t = get_times()
603 603 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
604 604 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
605 605 atexit.register(print_time)
606 606
607 607 uis = set([ui, lui])
608 608
609 609 if req.repo:
610 610 uis.add(req.repo.ui)
611 611
612 612 # copy configs that were passed on the cmdline (--config) to the repo ui
613 613 for cfg in cfgs:
614 614 req.repo.ui.setconfig(*cfg)
615 615
616 616 if options['verbose'] or options['debug'] or options['quiet']:
617 617 for opt in ('verbose', 'debug', 'quiet'):
618 618 val = str(bool(options[opt]))
619 619 for ui_ in uis:
620 620 ui_.setconfig('ui', opt, val)
621 621
622 622 if options['traceback']:
623 623 for ui_ in uis:
624 624 ui_.setconfig('ui', 'traceback', 'on')
625 625
626 626 if options['noninteractive']:
627 627 for ui_ in uis:
628 628 ui_.setconfig('ui', 'interactive', 'off')
629 629
630 630 if cmdoptions.get('insecure', False):
631 631 for ui_ in uis:
632 632 ui_.setconfig('web', 'cacerts', '')
633 633
634 634 if options['version']:
635 635 return commands.version_(ui)
636 636 if options['help']:
637 637 return commands.help_(ui, cmd)
638 638 elif not cmd:
639 639 return commands.help_(ui, 'shortlist')
640 640
641 641 repo = None
642 642 cmdpats = args[:]
643 643 if cmd not in commands.norepo.split():
644 644 # use the repo from the request only if we don't have -R
645 645 if not rpath and not cwd:
646 646 repo = req.repo
647 647
648 648 if repo:
649 649 # set the descriptors of the repo ui to those of ui
650 650 repo.ui.fin = ui.fin
651 651 repo.ui.fout = ui.fout
652 652 repo.ui.ferr = ui.ferr
653 653 else:
654 654 try:
655 655 repo = hg.repository(ui, path=path)
656 656 if not repo.local():
657 657 raise util.Abort(_("repository '%s' is not local") % path)
658 658 repo.ui.setconfig("bundle", "mainreporoot", repo.root)
659 659 except error.RequirementError:
660 660 raise
661 661 except error.RepoError:
662 662 if cmd not in commands.optionalrepo.split():
663 663 if args and not path: # try to infer -R from command args
664 664 repos = map(cmdutil.findrepo, args)
665 665 guess = repos[0]
666 666 if guess and repos.count(guess) == len(repos):
667 667 req.args = ['--repository', guess] + fullargs
668 668 return _dispatch(req)
669 669 if not path:
670 670 raise error.RepoError(_("no repository found in '%s'"
671 671 " (.hg not found)") % os.getcwd())
672 672 raise
673 673 if repo:
674 674 ui = repo.ui
675 675 args.insert(0, repo)
676 676 elif rpath:
677 677 ui.warn(_("warning: --repository ignored\n"))
678 678
679 679 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
680 680 ui.log("command", msg + "\n")
681 681 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
682 682 try:
683 683 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
684 684 cmdpats, cmdoptions)
685 685 finally:
686 686 if repo and repo != req.repo:
687 687 repo.close()
688 688
689 689 def _runcommand(ui, options, cmd, cmdfunc):
690 690 def checkargs():
691 691 try:
692 692 return cmdfunc()
693 693 except error.SignatureError:
694 694 raise error.CommandError(cmd, _("invalid arguments"))
695 695
696 696 if options['profile']:
697 697 format = ui.config('profiling', 'format', default='text')
698 698
699 699 if not format in ['text', 'kcachegrind']:
700 700 ui.warn(_("unrecognized profiling format '%s'"
701 701 " - Ignored\n") % format)
702 702 format = 'text'
703 703
704 704 output = ui.config('profiling', 'output')
705 705
706 706 if output:
707 707 path = ui.expandpath(output)
708 708 ostream = open(path, 'wb')
709 709 else:
710 710 ostream = sys.stderr
711 711
712 712 try:
713 713 from mercurial import lsprof
714 714 except ImportError:
715 715 raise util.Abort(_(
716 716 'lsprof not available - install from '
717 717 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
718 718 p = lsprof.Profiler()
719 719 p.enable(subcalls=True)
720 720 try:
721 721 return checkargs()
722 722 finally:
723 723 p.disable()
724 724
725 725 if format == 'kcachegrind':
726 726 import lsprofcalltree
727 727 calltree = lsprofcalltree.KCacheGrind(p)
728 728 calltree.output(ostream)
729 729 else:
730 730 # format == 'text'
731 731 stats = lsprof.Stats(p.getstats())
732 732 stats.sort()
733 733 stats.pprint(top=10, file=ostream, climit=5)
734 734
735 735 if output:
736 736 ostream.close()
737 737 else:
738 738 return checkargs()
@@ -1,66 +1,66 b''
1 1 Valid URLs are of the form::
2 2
3 3 local/filesystem/path[#revision]
4 file://local/filesystem/path[#revision]
4 file://localhost/filesystem/path[#revision]
5 5 http://[user[:pass]@]host[:port]/[path][#revision]
6 6 https://[user[:pass]@]host[:port]/[path][#revision]
7 7 ssh://[user@]host[:port]/[path][#revision]
8 8
9 9 Paths in the local filesystem can either point to Mercurial
10 10 repositories or to bundle files (as created by :hg:`bundle` or :hg:`
11 11 incoming --bundle`). See also :hg:`help paths`.
12 12
13 13 An optional identifier after # indicates a particular branch, tag, or
14 14 changeset to use from the remote repository. See also :hg:`help
15 15 revisions`.
16 16
17 17 Some features, such as pushing to http:// and https:// URLs are only
18 18 possible if the feature is explicitly enabled on the remote Mercurial
19 19 server.
20 20
21 21 Note that the security of HTTPS URLs depends on proper configuration of
22 22 web.cacerts.
23 23
24 24 Some notes about using SSH with Mercurial:
25 25
26 26 - SSH requires an accessible shell account on the destination machine
27 27 and a copy of hg in the remote path or specified with as remotecmd.
28 28 - path is relative to the remote user's home directory by default. Use
29 29 an extra slash at the start of a path to specify an absolute path::
30 30
31 31 ssh://example.com//tmp/repository
32 32
33 33 - Mercurial doesn't use its own compression via SSH; the right thing
34 34 to do is to configure it in your ~/.ssh/config, e.g.::
35 35
36 36 Host *.mylocalnetwork.example.com
37 37 Compression no
38 38 Host *
39 39 Compression yes
40 40
41 41 Alternatively specify "ssh -C" as your ssh command in your
42 42 configuration file or with the --ssh command line option.
43 43
44 44 These URLs can all be stored in your configuration file with path
45 45 aliases under the [paths] section like so::
46 46
47 47 [paths]
48 48 alias1 = URL1
49 49 alias2 = URL2
50 50 ...
51 51
52 52 You can then use the alias for any command that uses a URL (for
53 53 example :hg:`pull alias1` will be treated as :hg:`pull URL1`).
54 54
55 55 Two path aliases are special because they are used as defaults when
56 56 you do not provide the URL to a command:
57 57
58 58 default:
59 59 When you create a repository with hg clone, the clone command saves
60 60 the location of the source repository as the new repository's
61 61 'default' path. This is then used when you omit path from push- and
62 62 pull-like commands (including incoming and outgoing).
63 63
64 64 default-push:
65 65 The push command will look for a path named 'default-push', and
66 66 prefer it over 'default' if both are defined.
@@ -1,172 +1,173 b''
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys
10 10 import extensions, util
11 11
12 12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
13 13 '''call python hook. hook is callable object, looked up as
14 14 name in python module. if callable returns "true", hook
15 15 fails, else passes. if hook raises exception, treated as
16 16 hook failure. exception propagates if throw is "true".
17 17
18 18 reason for "true" meaning "hook failed" is so that
19 19 unmodified commands (e.g. mercurial.commands.update) can
20 20 be run as hooks without wrappers to convert return values.'''
21 21
22 22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 23 obj = funcname
24 24 if not util.safehasattr(obj, '__call__'):
25 25 d = funcname.rfind('.')
26 26 if d == -1:
27 27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 28 'a module)') % (hname, funcname))
29 29 modname = funcname[:d]
30 30 oldpaths = sys.path
31 31 if util.mainfrozen():
32 32 # binary installs require sys.path manipulation
33 33 modpath, modfile = os.path.split(modname)
34 34 if modpath and modfile:
35 35 sys.path = sys.path[:] + [modpath]
36 36 modname = modfile
37 37 try:
38 38 obj = __import__(modname)
39 39 except ImportError:
40 40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
41 41 try:
42 42 # extensions are loaded with hgext_ prefix
43 43 obj = __import__("hgext_%s" % modname)
44 44 except ImportError:
45 45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
46 46 if ui.tracebackflag:
47 47 ui.warn(_('exception from first failed import attempt:\n'))
48 48 ui.traceback(e1)
49 49 if ui.tracebackflag:
50 50 ui.warn(_('exception from second failed import attempt:\n'))
51 51 ui.traceback(e2)
52 52 raise util.Abort(_('%s hook is invalid '
53 53 '(import of "%s" failed)') %
54 54 (hname, modname))
55 55 sys.path = oldpaths
56 56 try:
57 57 for p in funcname.split('.')[1:]:
58 58 obj = getattr(obj, p)
59 59 except AttributeError:
60 60 raise util.Abort(_('%s hook is invalid '
61 61 '("%s" is not defined)') %
62 62 (hname, funcname))
63 63 if not util.safehasattr(obj, '__call__'):
64 64 raise util.Abort(_('%s hook is invalid '
65 65 '("%s" is not callable)') %
66 66 (hname, funcname))
67 67 try:
68 68 try:
69 69 # redirect IO descriptors the the ui descriptors so hooks
70 70 # that write directly to these don't mess up the command
71 71 # protocol when running through the command server
72 72 old = sys.stdout, sys.stderr, sys.stdin
73 73 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
74 74
75 75 r = obj(ui=ui, repo=repo, hooktype=name, **args)
76 76 except KeyboardInterrupt:
77 77 raise
78 78 except Exception, exc:
79 79 if isinstance(exc, util.Abort):
80 80 ui.warn(_('error: %s hook failed: %s\n') %
81 81 (hname, exc.args[0]))
82 82 else:
83 83 ui.warn(_('error: %s hook raised an exception: '
84 84 '%s\n') % (hname, exc))
85 85 if throw:
86 86 raise
87 87 ui.traceback()
88 88 return True
89 89 finally:
90 90 sys.stdout, sys.stderr, sys.stdin = old
91 91 if r:
92 92 if throw:
93 93 raise util.Abort(_('%s hook failed') % hname)
94 94 ui.warn(_('warning: %s hook failed\n') % hname)
95 95 return r
96 96
97 97 def _exthook(ui, repo, name, cmd, args, throw):
98 98 ui.note(_("running hook %s: %s\n") % (name, cmd))
99 99
100 100 env = {}
101 101 for k, v in args.iteritems():
102 102 if util.safehasattr(v, '__call__'):
103 103 v = v()
104 104 if isinstance(v, dict):
105 105 # make the dictionary element order stable across Python
106 106 # implementations
107 107 v = ('{' +
108 108 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
109 109 '}')
110 110 env['HG_' + k.upper()] = v
111 111
112 112 if repo:
113 113 cwd = repo.root
114 114 else:
115 115 cwd = os.getcwd()
116 116 if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'):
117 117 r = util.system(cmd, environ=env, cwd=cwd, out=ui)
118 118 else:
119 119 r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout)
120 120 if r:
121 121 desc, r = util.explainexit(r)
122 122 if throw:
123 123 raise util.Abort(_('%s hook %s') % (name, desc))
124 124 ui.warn(_('warning: %s hook %s\n') % (name, desc))
125 125 return r
126 126
127 127 _redirect = False
128 128 def redirect(state):
129 129 global _redirect
130 130 _redirect = state
131 131
132 132 def hook(ui, repo, name, throw=False, **args):
133 133 r = False
134 134
135 135 oldstdout = -1
136 136 if _redirect:
137 137 try:
138 138 stdoutno = sys.__stdout__.fileno()
139 139 stderrno = sys.__stderr__.fileno()
140 140 # temporarily redirect stdout to stderr, if possible
141 141 if stdoutno >= 0 and stderrno >= 0:
142 sys.__stdout__.flush()
142 143 oldstdout = os.dup(stdoutno)
143 144 os.dup2(stderrno, stdoutno)
144 145 except AttributeError:
145 146 # __stdout/err__ doesn't have fileno(), it's not a real file
146 147 pass
147 148
148 149 try:
149 150 for hname, cmd in ui.configitems('hooks'):
150 151 if hname.split('.')[0] != name or not cmd:
151 152 continue
152 153 if util.safehasattr(cmd, '__call__'):
153 154 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
154 155 elif cmd.startswith('python:'):
155 156 if cmd.count(':') >= 2:
156 157 path, cmd = cmd[7:].rsplit(':', 1)
157 158 path = util.expandpath(path)
158 159 if repo:
159 160 path = os.path.join(repo.root, path)
160 161 mod = extensions.loadpath(path, 'hghook.%s' % hname)
161 162 hookfn = getattr(mod, cmd)
162 163 else:
163 164 hookfn = cmd[7:].strip()
164 165 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
165 166 else:
166 167 r = _exthook(ui, repo, hname, cmd, args, throw) or r
167 168 finally:
168 169 if _redirect and oldstdout >= 0:
169 170 os.dup2(oldstdout, stdoutno)
170 171 os.close(oldstdout)
171 172
172 173 return r
@@ -1,290 +1,290 b''
1 1 # mdiff.py - diff and patch routines for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import bdiff, mpatch, util
10 10 import re, struct
11 11
12 12 def splitnewlines(text):
13 13 '''like str.splitlines, but only split on newlines.'''
14 14 lines = [l + '\n' for l in text.split('\n')]
15 15 if lines:
16 16 if lines[-1] == '\n':
17 17 lines.pop()
18 18 else:
19 19 lines[-1] = lines[-1][:-1]
20 20 return lines
21 21
22 22 class diffopts(object):
23 23 '''context is the number of context lines
24 24 text treats all files as text
25 25 showfunc enables diff -p output
26 26 git enables the git extended patch format
27 27 nodates removes dates from diff headers
28 28 ignorews ignores all whitespace changes in the diff
29 29 ignorewsamount ignores changes in the amount of whitespace
30 30 ignoreblanklines ignores changes whose lines are all blank
31 31 upgrade generates git diffs to avoid data loss
32 32 '''
33 33
34 34 defaults = {
35 35 'context': 3,
36 36 'text': False,
37 37 'showfunc': False,
38 38 'git': False,
39 39 'nodates': False,
40 40 'ignorews': False,
41 41 'ignorewsamount': False,
42 42 'ignoreblanklines': False,
43 43 'upgrade': False,
44 44 }
45 45
46 46 __slots__ = defaults.keys()
47 47
48 48 def __init__(self, **opts):
49 49 for k in self.__slots__:
50 50 v = opts.get(k)
51 51 if v is None:
52 52 v = self.defaults[k]
53 53 setattr(self, k, v)
54 54
55 55 try:
56 56 self.context = int(self.context)
57 57 except ValueError:
58 58 raise util.Abort(_('diff context lines count must be '
59 59 'an integer, not %r') % self.context)
60 60
61 61 def copy(self, **kwargs):
62 62 opts = dict((k, getattr(self, k)) for k in self.defaults)
63 63 opts.update(kwargs)
64 64 return diffopts(**opts)
65 65
66 66 defaultopts = diffopts()
67 67
68 68 def wsclean(opts, text, blank=True):
69 69 if opts.ignorews:
70 70 text = re.sub('[ \t\r]+', '', text)
71 71 elif opts.ignorewsamount:
72 72 text = re.sub('[ \t\r]+', ' ', text)
73 73 text = text.replace(' \n', '\n')
74 74 if blank and opts.ignoreblanklines:
75 text = re.sub('\n+', '', text)
75 text = re.sub('\n+', '\n', text).strip('\n')
76 76 return text
77 77
78 78 def diffline(revs, a, b, opts):
79 79 parts = ['diff']
80 80 if opts.git:
81 81 parts.append('--git')
82 82 if revs and not opts.git:
83 83 parts.append(' '.join(["-r %s" % rev for rev in revs]))
84 84 if opts.git:
85 85 parts.append('a/%s' % a)
86 86 parts.append('b/%s' % b)
87 87 else:
88 88 parts.append(a)
89 89 return ' '.join(parts) + '\n'
90 90
91 91 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
92 92 def datetag(date, addtab=True):
93 93 if not opts.git and not opts.nodates:
94 94 return '\t%s\n' % date
95 95 if addtab and ' ' in fn1:
96 96 return '\t\n'
97 97 return '\n'
98 98
99 99 if not a and not b:
100 100 return ""
101 101 epoch = util.datestr((0, 0))
102 102
103 103 fn1 = util.pconvert(fn1)
104 104 fn2 = util.pconvert(fn2)
105 105
106 106 if not opts.text and (util.binary(a) or util.binary(b)):
107 107 if a and b and len(a) == len(b) and a == b:
108 108 return ""
109 109 l = ['Binary file %s has changed\n' % fn1]
110 110 elif not a:
111 111 b = splitnewlines(b)
112 112 if a is None:
113 113 l1 = '--- /dev/null%s' % datetag(epoch, False)
114 114 else:
115 115 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
116 116 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
117 117 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
118 118 l = [l1, l2, l3] + ["+" + e for e in b]
119 119 elif not b:
120 120 a = splitnewlines(a)
121 121 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
122 122 if b is None:
123 123 l2 = '+++ /dev/null%s' % datetag(epoch, False)
124 124 else:
125 125 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
126 126 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
127 127 l = [l1, l2, l3] + ["-" + e for e in a]
128 128 else:
129 129 al = splitnewlines(a)
130 130 bl = splitnewlines(b)
131 131 l = list(_unidiff(a, b, al, bl, opts=opts))
132 132 if not l:
133 133 return ""
134 134
135 135 l.insert(0, "--- a/%s%s" % (fn1, datetag(ad)))
136 136 l.insert(1, "+++ b/%s%s" % (fn2, datetag(bd)))
137 137
138 138 for ln in xrange(len(l)):
139 139 if l[ln][-1] != '\n':
140 140 l[ln] += "\n\ No newline at end of file\n"
141 141
142 142 if r:
143 143 l.insert(0, diffline(r, fn1, fn2, opts))
144 144
145 145 return "".join(l)
146 146
147 147 # creates a headerless unified diff
148 148 # t1 and t2 are the text to be diffed
149 149 # l1 and l2 are the text broken up into lines
150 150 def _unidiff(t1, t2, l1, l2, opts=defaultopts):
151 151 def contextend(l, len):
152 152 ret = l + opts.context
153 153 if ret > len:
154 154 ret = len
155 155 return ret
156 156
157 157 def contextstart(l):
158 158 ret = l - opts.context
159 159 if ret < 0:
160 160 return 0
161 161 return ret
162 162
163 163 lastfunc = [0, '']
164 164 def yieldhunk(hunk):
165 165 (astart, a2, bstart, b2, delta) = hunk
166 166 aend = contextend(a2, len(l1))
167 167 alen = aend - astart
168 168 blen = b2 - bstart + aend - a2
169 169
170 170 func = ""
171 171 if opts.showfunc:
172 172 lastpos, func = lastfunc
173 173 # walk backwards from the start of the context up to the start of
174 174 # the previous hunk context until we find a line starting with an
175 175 # alphanumeric char.
176 176 for i in xrange(astart - 1, lastpos - 1, -1):
177 177 if l1[i][0].isalnum():
178 178 func = ' ' + l1[i].rstrip()[:40]
179 179 lastfunc[1] = func
180 180 break
181 181 # by recording this hunk's starting point as the next place to
182 182 # start looking for function lines, we avoid reading any line in
183 183 # the file more than once.
184 184 lastfunc[0] = astart
185 185
186 186 # zero-length hunk ranges report their start line as one less
187 187 if alen:
188 188 astart += 1
189 189 if blen:
190 190 bstart += 1
191 191
192 192 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart, alen,
193 193 bstart, blen, func)
194 194 for x in delta:
195 195 yield x
196 196 for x in xrange(a2, aend):
197 197 yield ' ' + l1[x]
198 198
199 199 # bdiff.blocks gives us the matching sequences in the files. The loop
200 200 # below finds the spaces between those matching sequences and translates
201 201 # them into diff output.
202 202 #
203 203 if opts.ignorews or opts.ignorewsamount:
204 204 t1 = wsclean(opts, t1, False)
205 205 t2 = wsclean(opts, t2, False)
206 206
207 207 diff = bdiff.blocks(t1, t2)
208 208 hunk = None
209 209 for i, s1 in enumerate(diff):
210 210 # The first match is special.
211 211 # we've either found a match starting at line 0 or a match later
212 212 # in the file. If it starts later, old and new below will both be
213 213 # empty and we'll continue to the next match.
214 214 if i > 0:
215 215 s = diff[i - 1]
216 216 else:
217 217 s = [0, 0, 0, 0]
218 218 delta = []
219 219 a1 = s[1]
220 220 a2 = s1[0]
221 221 b1 = s[3]
222 222 b2 = s1[2]
223 223
224 224 old = l1[a1:a2]
225 225 new = l2[b1:b2]
226 226
227 227 # bdiff sometimes gives huge matches past eof, this check eats them,
228 228 # and deals with the special first match case described above
229 229 if not old and not new:
230 230 continue
231 231
232 232 if opts.ignoreblanklines:
233 233 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
234 234 continue
235 235
236 236 astart = contextstart(a1)
237 237 bstart = contextstart(b1)
238 238 prev = None
239 239 if hunk:
240 240 # join with the previous hunk if it falls inside the context
241 241 if astart < hunk[1] + opts.context + 1:
242 242 prev = hunk
243 243 astart = hunk[1]
244 244 bstart = hunk[3]
245 245 else:
246 246 for x in yieldhunk(hunk):
247 247 yield x
248 248 if prev:
249 249 # we've joined the previous hunk, record the new ending points.
250 250 hunk[1] = a2
251 251 hunk[3] = b2
252 252 delta = hunk[4]
253 253 else:
254 254 # create a new hunk
255 255 hunk = [astart, a2, bstart, b2, delta]
256 256
257 257 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
258 258 delta[len(delta):] = ['-' + x for x in old]
259 259 delta[len(delta):] = ['+' + x for x in new]
260 260
261 261 if hunk:
262 262 for x in yieldhunk(hunk):
263 263 yield x
264 264
265 265 def patchtext(bin):
266 266 pos = 0
267 267 t = []
268 268 while pos < len(bin):
269 269 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
270 270 pos += 12
271 271 t.append(bin[pos:pos + l])
272 272 pos += l
273 273 return "".join(t)
274 274
275 275 def patch(a, bin):
276 276 if len(a) == 0:
277 277 # skip over trivial delta header
278 278 return buffer(bin, 12)
279 279 return mpatch.patches(a, [bin])
280 280
281 281 # similar to difflib.SequenceMatcher.get_matching_blocks
282 282 def get_matching_blocks(a, b):
283 283 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
284 284
285 285 def trivialdiffheader(length):
286 286 return struct.pack(">lll", 0, 0, length)
287 287
288 288 patches = mpatch.patches
289 289 patchedsize = mpatch.patchedsize
290 290 textdiff = bdiff.bdiff
@@ -1,194 +1,194 b''
1 1 # setdiscovery.py - improved discovery of common nodeset for mercurial
2 2 #
3 3 # Copyright 2010 Benoit Boissinot <bboissin@gmail.com>
4 4 # and Peter Arrenbrecht <peter@arrenbrecht.ch>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from node import nullid
10 10 from i18n import _
11 11 import random, collections, util, dagutil
12 12
13 13 def _updatesample(dag, nodes, sample, always, quicksamplesize=0):
14 14 # if nodes is empty we scan the entire graph
15 15 if nodes:
16 16 heads = dag.headsetofconnecteds(nodes)
17 17 else:
18 18 heads = dag.heads()
19 19 dist = {}
20 20 visit = collections.deque(heads)
21 21 seen = set()
22 22 factor = 1
23 23 while visit:
24 24 curr = visit.popleft()
25 25 if curr in seen:
26 26 continue
27 27 d = dist.setdefault(curr, 1)
28 28 if d > factor:
29 29 factor *= 2
30 30 if d == factor:
31 31 if curr not in always: # need this check for the early exit below
32 32 sample.add(curr)
33 33 if quicksamplesize and (len(sample) >= quicksamplesize):
34 34 return
35 35 seen.add(curr)
36 36 for p in dag.parents(curr):
37 37 if not nodes or p in nodes:
38 38 dist.setdefault(p, d + 1)
39 39 visit.append(p)
40 40
41 41 def _setupsample(dag, nodes, size):
42 42 if len(nodes) <= size:
43 43 return set(nodes), None, 0
44 44 always = dag.headsetofconnecteds(nodes)
45 45 desiredlen = size - len(always)
46 46 if desiredlen <= 0:
47 47 # This could be bad if there are very many heads, all unknown to the
48 48 # server. We're counting on long request support here.
49 49 return always, None, desiredlen
50 50 return always, set(), desiredlen
51 51
52 52 def _takequicksample(dag, nodes, size, initial):
53 53 always, sample, desiredlen = _setupsample(dag, nodes, size)
54 54 if sample is None:
55 55 return always
56 56 if initial:
57 57 fromset = None
58 58 else:
59 59 fromset = nodes
60 60 _updatesample(dag, fromset, sample, always, quicksamplesize=desiredlen)
61 61 sample.update(always)
62 62 return sample
63 63
64 64 def _takefullsample(dag, nodes, size):
65 65 always, sample, desiredlen = _setupsample(dag, nodes, size)
66 66 if sample is None:
67 67 return always
68 68 # update from heads
69 69 _updatesample(dag, nodes, sample, always)
70 70 # update from roots
71 71 _updatesample(dag.inverse(), nodes, sample, always)
72 72 assert sample
73 73 if len(sample) > desiredlen:
74 74 sample = set(random.sample(sample, desiredlen))
75 75 elif len(sample) < desiredlen:
76 76 more = desiredlen - len(sample)
77 77 sample.update(random.sample(list(nodes - sample - always), more))
78 78 sample.update(always)
79 79 return sample
80 80
81 81 def findcommonheads(ui, local, remote,
82 82 initialsamplesize=100,
83 83 fullsamplesize=200,
84 84 abortwhenunrelated=True):
85 85 '''Return a tuple (common, anyincoming, remoteheads) used to identify
86 86 missing nodes from or in remote.
87 87
88 88 shortcutlocal determines whether we try use direct access to localrepo if
89 89 remote is actually local.
90 90 '''
91 91 roundtrips = 0
92 92 cl = local.changelog
93 93 dag = dagutil.revlogdag(cl)
94 94
95 95 # early exit if we know all the specified remote heads already
96 96 ui.debug("query 1; heads\n")
97 97 roundtrips += 1
98 98 ownheads = dag.heads()
99 99 sample = ownheads
100 100 if remote.local():
101 101 # stopgap until we have a proper localpeer that supports batch()
102 102 srvheadhashes = remote.heads()
103 103 yesno = remote.known(dag.externalizeall(sample))
104 104 elif remote.capable('batch'):
105 105 batch = remote.batch()
106 106 srvheadhashesref = batch.heads()
107 107 yesnoref = batch.known(dag.externalizeall(sample))
108 108 batch.submit()
109 109 srvheadhashes = srvheadhashesref.value
110 110 yesno = yesnoref.value
111 111 else:
112 112 # compatibitity with pre-batch, but post-known remotes during 1.9 devel
113 113 srvheadhashes = remote.heads()
114 114 sample = []
115 115
116 116 if cl.tip() == nullid:
117 117 if srvheadhashes != [nullid]:
118 118 return [nullid], True, srvheadhashes
119 119 return [nullid], False, []
120 120
121 121 # start actual discovery (we note this before the next "if" for
122 122 # compatibility reasons)
123 123 ui.status(_("searching for changes\n"))
124 124
125 125 srvheads = dag.internalizeall(srvheadhashes, filterunknown=True)
126 126 if len(srvheads) == len(srvheadhashes):
127 127 ui.debug("all remote heads known locally\n")
128 128 return (srvheadhashes, False, srvheadhashes,)
129 129
130 130 if sample and util.all(yesno):
131 ui.note("all local heads known remotely\n")
131 ui.note(_("all local heads known remotely\n"))
132 132 ownheadhashes = dag.externalizeall(ownheads)
133 133 return (ownheadhashes, True, srvheadhashes,)
134 134
135 135 # full blown discovery
136 136 undecided = dag.nodeset() # own nodes where I don't know if remote knows them
137 137 common = set() # own nodes I know we both know
138 138 missing = set() # own nodes I know remote lacks
139 139
140 140 # treat remote heads (and maybe own heads) as a first implicit sample response
141 141 common.update(dag.ancestorset(srvheads))
142 142 undecided.difference_update(common)
143 143
144 144 full = False
145 145 while undecided:
146 146
147 147 if sample:
148 148 commoninsample = set(n for i, n in enumerate(sample) if yesno[i])
149 149 common.update(dag.ancestorset(commoninsample, common))
150 150
151 151 missinginsample = [n for i, n in enumerate(sample) if not yesno[i]]
152 152 missing.update(dag.descendantset(missinginsample, missing))
153 153
154 154 undecided.difference_update(missing)
155 155 undecided.difference_update(common)
156 156
157 157 if not undecided:
158 158 break
159 159
160 160 if full:
161 ui.note("sampling from both directions\n")
161 ui.note(_("sampling from both directions\n"))
162 162 sample = _takefullsample(dag, undecided, size=fullsamplesize)
163 163 elif common:
164 164 # use cheapish initial sample
165 165 ui.debug("taking initial sample\n")
166 166 sample = _takefullsample(dag, undecided, size=fullsamplesize)
167 167 else:
168 168 # use even cheaper initial sample
169 169 ui.debug("taking quick initial sample\n")
170 170 sample = _takequicksample(dag, undecided, size=initialsamplesize,
171 171 initial=True)
172 172
173 173 roundtrips += 1
174 174 ui.progress(_('searching'), roundtrips, unit=_('queries'))
175 175 ui.debug("query %i; still undecided: %i, sample size is: %i\n"
176 176 % (roundtrips, len(undecided), len(sample)))
177 177 # indices between sample and externalized version must match
178 178 sample = list(sample)
179 179 yesno = remote.known(dag.externalizeall(sample))
180 180 full = True
181 181
182 182 result = dag.headsetofconnecteds(common)
183 183 ui.progress(_('searching'), None)
184 184 ui.debug("%d total queries\n" % roundtrips)
185 185
186 186 if not result and srvheadhashes != [nullid]:
187 187 if abortwhenunrelated:
188 188 raise util.Abort(_("repository is unrelated"))
189 189 else:
190 190 ui.warn(_("warning: repository is unrelated\n"))
191 191 return (set([nullid]), True, srvheadhashes,)
192 192
193 193 anyincoming = (srvheadhashes != [nullid])
194 194 return dag.externalizeall(result), anyincoming, srvheadhashes
@@ -1,1133 +1,1133 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import errno, os, re, xml.dom.minidom, shutil, posixpath
9 9 import stat, subprocess, tarfile
10 10 from i18n import _
11 11 import config, scmutil, util, node, error, cmdutil, bookmarks
12 12 hg = None
13 13 propertycache = util.propertycache
14 14
15 15 nullstate = ('', '', 'empty')
16 16
17 17 def state(ctx, ui):
18 18 """return a state dict, mapping subrepo paths configured in .hgsub
19 19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
20 20 (key in types dict))
21 21 """
22 22 p = config.config()
23 23 def read(f, sections=None, remap=None):
24 24 if f in ctx:
25 25 try:
26 26 data = ctx[f].data()
27 27 except IOError, err:
28 28 if err.errno != errno.ENOENT:
29 29 raise
30 30 # handle missing subrepo spec files as removed
31 31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
32 32 return
33 33 p.parse(f, data, sections, remap, read)
34 34 else:
35 35 raise util.Abort(_("subrepo spec file %s not found") % f)
36 36
37 37 if '.hgsub' in ctx:
38 38 read('.hgsub')
39 39
40 40 for path, src in ui.configitems('subpaths'):
41 41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
42 42
43 43 rev = {}
44 44 if '.hgsubstate' in ctx:
45 45 try:
46 46 for l in ctx['.hgsubstate'].data().splitlines():
47 47 revision, path = l.split(" ", 1)
48 48 rev[path] = revision
49 49 except IOError, err:
50 50 if err.errno != errno.ENOENT:
51 51 raise
52 52
53 53 def remap(src):
54 54 for pattern, repl in p.items('subpaths'):
55 55 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
56 56 # does a string decode.
57 57 repl = repl.encode('string-escape')
58 58 # However, we still want to allow back references to go
59 59 # through unharmed, so we turn r'\\1' into r'\1'. Again,
60 60 # extra escapes are needed because re.sub string decodes.
61 61 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
62 62 try:
63 63 src = re.sub(pattern, repl, src, 1)
64 64 except re.error, e:
65 65 raise util.Abort(_("bad subrepository pattern in %s: %s")
66 66 % (p.source('subpaths', pattern), e))
67 67 return src
68 68
69 69 state = {}
70 70 for path, src in p[''].items():
71 71 kind = 'hg'
72 72 if src.startswith('['):
73 73 if ']' not in src:
74 74 raise util.Abort(_('missing ] in subrepo source'))
75 75 kind, src = src.split(']', 1)
76 76 kind = kind[1:]
77 77 src = src.lstrip() # strip any extra whitespace after ']'
78 78
79 79 if not util.url(src).isabs():
80 80 parent = _abssource(ctx._repo, abort=False)
81 81 if parent:
82 82 parent = util.url(parent)
83 83 parent.path = posixpath.join(parent.path or '', src)
84 84 parent.path = posixpath.normpath(parent.path)
85 85 joined = str(parent)
86 86 # Remap the full joined path and use it if it changes,
87 87 # else remap the original source.
88 88 remapped = remap(joined)
89 89 if remapped == joined:
90 90 src = remap(src)
91 91 else:
92 92 src = remapped
93 93
94 94 src = remap(src)
95 95 state[path] = (src.strip(), rev.get(path, ''), kind)
96 96
97 97 return state
98 98
99 99 def writestate(repo, state):
100 100 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
101 101 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
102 102 repo.wwrite('.hgsubstate', ''.join(lines), '')
103 103
104 104 def submerge(repo, wctx, mctx, actx, overwrite):
105 105 """delegated from merge.applyupdates: merging of .hgsubstate file
106 106 in working context, merging context and ancestor context"""
107 107 if mctx == actx: # backwards?
108 108 actx = wctx.p1()
109 109 s1 = wctx.substate
110 110 s2 = mctx.substate
111 111 sa = actx.substate
112 112 sm = {}
113 113
114 114 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
115 115
116 116 def debug(s, msg, r=""):
117 117 if r:
118 118 r = "%s:%s:%s" % r
119 119 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
120 120
121 121 for s, l in s1.items():
122 122 a = sa.get(s, nullstate)
123 123 ld = l # local state with possible dirty flag for compares
124 124 if wctx.sub(s).dirty():
125 125 ld = (l[0], l[1] + "+")
126 126 if wctx == actx: # overwrite
127 127 a = ld
128 128
129 129 if s in s2:
130 130 r = s2[s]
131 131 if ld == r or r == a: # no change or local is newer
132 132 sm[s] = l
133 133 continue
134 134 elif ld == a: # other side changed
135 135 debug(s, "other changed, get", r)
136 136 wctx.sub(s).get(r, overwrite)
137 137 sm[s] = r
138 138 elif ld[0] != r[0]: # sources differ
139 139 if repo.ui.promptchoice(
140 140 _(' subrepository sources for %s differ\n'
141 141 'use (l)ocal source (%s) or (r)emote source (%s)?')
142 142 % (s, l[0], r[0]),
143 143 (_('&Local'), _('&Remote')), 0):
144 144 debug(s, "prompt changed, get", r)
145 145 wctx.sub(s).get(r, overwrite)
146 146 sm[s] = r
147 147 elif ld[1] == a[1]: # local side is unchanged
148 148 debug(s, "other side changed, get", r)
149 149 wctx.sub(s).get(r, overwrite)
150 150 sm[s] = r
151 151 else:
152 152 debug(s, "both sides changed, merge with", r)
153 153 wctx.sub(s).merge(r)
154 154 sm[s] = l
155 155 elif ld == a: # remote removed, local unchanged
156 156 debug(s, "remote removed, remove")
157 157 wctx.sub(s).remove()
158 158 elif a == nullstate: # not present in remote or ancestor
159 159 debug(s, "local added, keep")
160 160 sm[s] = l
161 161 continue
162 162 else:
163 163 if repo.ui.promptchoice(
164 164 _(' local changed subrepository %s which remote removed\n'
165 165 'use (c)hanged version or (d)elete?') % s,
166 166 (_('&Changed'), _('&Delete')), 0):
167 167 debug(s, "prompt remove")
168 168 wctx.sub(s).remove()
169 169
170 170 for s, r in sorted(s2.items()):
171 171 if s in s1:
172 172 continue
173 173 elif s not in sa:
174 174 debug(s, "remote added, get", r)
175 175 mctx.sub(s).get(r)
176 176 sm[s] = r
177 177 elif r != sa[s]:
178 178 if repo.ui.promptchoice(
179 179 _(' remote changed subrepository %s which local removed\n'
180 180 'use (c)hanged version or (d)elete?') % s,
181 181 (_('&Changed'), _('&Delete')), 0) == 0:
182 182 debug(s, "prompt recreate", r)
183 183 wctx.sub(s).get(r)
184 184 sm[s] = r
185 185
186 186 # record merged .hgsubstate
187 187 writestate(repo, sm)
188 188
189 189 def _updateprompt(ui, sub, dirty, local, remote):
190 190 if dirty:
191 191 msg = (_(' subrepository sources for %s differ\n'
192 192 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
193 193 % (subrelpath(sub), local, remote))
194 194 else:
195 195 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
196 196 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
197 197 % (subrelpath(sub), local, remote))
198 198 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
199 199
200 200 def reporelpath(repo):
201 201 """return path to this (sub)repo as seen from outermost repo"""
202 202 parent = repo
203 203 while util.safehasattr(parent, '_subparent'):
204 204 parent = parent._subparent
205 205 p = parent.root.rstrip(os.sep)
206 206 return repo.root[len(p) + 1:]
207 207
208 208 def subrelpath(sub):
209 209 """return path to this subrepo as seen from outermost repo"""
210 210 if util.safehasattr(sub, '_relpath'):
211 211 return sub._relpath
212 212 if not util.safehasattr(sub, '_repo'):
213 213 return sub._path
214 214 return reporelpath(sub._repo)
215 215
216 216 def _abssource(repo, push=False, abort=True):
217 217 """return pull/push path of repo - either based on parent repo .hgsub info
218 218 or on the top repo config. Abort or return None if no source found."""
219 219 if util.safehasattr(repo, '_subparent'):
220 220 source = util.url(repo._subsource)
221 221 if source.isabs():
222 222 return str(source)
223 223 source.path = posixpath.normpath(source.path)
224 224 parent = _abssource(repo._subparent, push, abort=False)
225 225 if parent:
226 parent = util.url(parent)
226 parent = util.url(util.pconvert(parent))
227 227 parent.path = posixpath.join(parent.path or '', source.path)
228 228 parent.path = posixpath.normpath(parent.path)
229 229 return str(parent)
230 230 else: # recursion reached top repo
231 231 if util.safehasattr(repo, '_subtoppath'):
232 232 return repo._subtoppath
233 233 if push and repo.ui.config('paths', 'default-push'):
234 234 return repo.ui.config('paths', 'default-push')
235 235 if repo.ui.config('paths', 'default'):
236 236 return repo.ui.config('paths', 'default')
237 237 if abort:
238 238 raise util.Abort(_("default path for subrepository %s not found") %
239 239 reporelpath(repo))
240 240
241 241 def itersubrepos(ctx1, ctx2):
242 242 """find subrepos in ctx1 or ctx2"""
243 243 # Create a (subpath, ctx) mapping where we prefer subpaths from
244 244 # ctx1. The subpaths from ctx2 are important when the .hgsub file
245 245 # has been modified (in ctx2) but not yet committed (in ctx1).
246 246 subpaths = dict.fromkeys(ctx2.substate, ctx2)
247 247 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
248 248 for subpath, ctx in sorted(subpaths.iteritems()):
249 249 yield subpath, ctx.sub(subpath)
250 250
251 251 def subrepo(ctx, path):
252 252 """return instance of the right subrepo class for subrepo in path"""
253 253 # subrepo inherently violates our import layering rules
254 254 # because it wants to make repo objects from deep inside the stack
255 255 # so we manually delay the circular imports to not break
256 256 # scripts that don't use our demand-loading
257 257 global hg
258 258 import hg as h
259 259 hg = h
260 260
261 261 scmutil.pathauditor(ctx._repo.root)(path)
262 262 state = ctx.substate.get(path, nullstate)
263 263 if state[2] not in types:
264 264 raise util.Abort(_('unknown subrepo type %s') % state[2])
265 265 return types[state[2]](ctx, path, state[:2])
266 266
267 267 # subrepo classes need to implement the following abstract class:
268 268
269 269 class abstractsubrepo(object):
270 270
271 271 def dirty(self, ignoreupdate=False):
272 272 """returns true if the dirstate of the subrepo is dirty or does not
273 273 match current stored state. If ignoreupdate is true, only check
274 274 whether the subrepo has uncommitted changes in its dirstate.
275 275 """
276 276 raise NotImplementedError
277 277
278 278 def checknested(self, path):
279 279 """check if path is a subrepository within this repository"""
280 280 return False
281 281
282 282 def commit(self, text, user, date):
283 283 """commit the current changes to the subrepo with the given
284 284 log message. Use given user and date if possible. Return the
285 285 new state of the subrepo.
286 286 """
287 287 raise NotImplementedError
288 288
289 289 def remove(self):
290 290 """remove the subrepo
291 291
292 292 (should verify the dirstate is not dirty first)
293 293 """
294 294 raise NotImplementedError
295 295
296 296 def get(self, state, overwrite=False):
297 297 """run whatever commands are needed to put the subrepo into
298 298 this state
299 299 """
300 300 raise NotImplementedError
301 301
302 302 def merge(self, state):
303 303 """merge currently-saved state with the new state."""
304 304 raise NotImplementedError
305 305
306 306 def push(self, force):
307 307 """perform whatever action is analogous to 'hg push'
308 308
309 309 This may be a no-op on some systems.
310 310 """
311 311 raise NotImplementedError
312 312
313 313 def add(self, ui, match, dryrun, prefix):
314 314 return []
315 315
316 316 def status(self, rev2, **opts):
317 317 return [], [], [], [], [], [], []
318 318
319 319 def diff(self, diffopts, node2, match, prefix, **opts):
320 320 pass
321 321
322 322 def outgoing(self, ui, dest, opts):
323 323 return 1
324 324
325 325 def incoming(self, ui, source, opts):
326 326 return 1
327 327
328 328 def files(self):
329 329 """return filename iterator"""
330 330 raise NotImplementedError
331 331
332 332 def filedata(self, name):
333 333 """return file data"""
334 334 raise NotImplementedError
335 335
336 336 def fileflags(self, name):
337 337 """return file flags"""
338 338 return ''
339 339
340 340 def archive(self, ui, archiver, prefix):
341 341 files = self.files()
342 342 total = len(files)
343 343 relpath = subrelpath(self)
344 344 ui.progress(_('archiving (%s)') % relpath, 0,
345 345 unit=_('files'), total=total)
346 346 for i, name in enumerate(files):
347 347 flags = self.fileflags(name)
348 348 mode = 'x' in flags and 0755 or 0644
349 349 symlink = 'l' in flags
350 350 archiver.addfile(os.path.join(prefix, self._path, name),
351 351 mode, symlink, self.filedata(name))
352 352 ui.progress(_('archiving (%s)') % relpath, i + 1,
353 353 unit=_('files'), total=total)
354 354 ui.progress(_('archiving (%s)') % relpath, None)
355 355
356 356 def walk(self, match):
357 357 '''
358 358 walk recursively through the directory tree, finding all files
359 359 matched by the match function
360 360 '''
361 361 pass
362 362
363 363 def forget(self, files):
364 364 pass
365 365
366 366 class hgsubrepo(abstractsubrepo):
367 367 def __init__(self, ctx, path, state):
368 368 self._path = path
369 369 self._state = state
370 370 r = ctx._repo
371 371 root = r.wjoin(path)
372 372 create = False
373 373 if not os.path.exists(os.path.join(root, '.hg')):
374 374 create = True
375 375 util.makedirs(root)
376 376 self._repo = hg.repository(r.ui, root, create=create)
377 377 self._initrepo(r, state[0], create)
378 378
379 379 def _initrepo(self, parentrepo, source, create):
380 380 self._repo._subparent = parentrepo
381 381 self._repo._subsource = source
382 382
383 383 if create:
384 384 fp = self._repo.opener("hgrc", "w", text=True)
385 385 fp.write('[paths]\n')
386 386
387 387 def addpathconfig(key, value):
388 388 if value:
389 389 fp.write('%s = %s\n' % (key, value))
390 390 self._repo.ui.setconfig('paths', key, value)
391 391
392 392 defpath = _abssource(self._repo, abort=False)
393 393 defpushpath = _abssource(self._repo, True, abort=False)
394 394 addpathconfig('default', defpath)
395 395 if defpath != defpushpath:
396 396 addpathconfig('default-push', defpushpath)
397 397 fp.close()
398 398
399 399 def add(self, ui, match, dryrun, prefix):
400 400 return cmdutil.add(ui, self._repo, match, dryrun, True,
401 401 os.path.join(prefix, self._path))
402 402
403 403 def status(self, rev2, **opts):
404 404 try:
405 405 rev1 = self._state[1]
406 406 ctx1 = self._repo[rev1]
407 407 ctx2 = self._repo[rev2]
408 408 return self._repo.status(ctx1, ctx2, **opts)
409 409 except error.RepoLookupError, inst:
410 410 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
411 411 % (inst, subrelpath(self)))
412 412 return [], [], [], [], [], [], []
413 413
414 414 def diff(self, diffopts, node2, match, prefix, **opts):
415 415 try:
416 416 node1 = node.bin(self._state[1])
417 417 # We currently expect node2 to come from substate and be
418 418 # in hex format
419 419 if node2 is not None:
420 420 node2 = node.bin(node2)
421 421 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
422 422 node1, node2, match,
423 423 prefix=os.path.join(prefix, self._path),
424 424 listsubrepos=True, **opts)
425 425 except error.RepoLookupError, inst:
426 426 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
427 427 % (inst, subrelpath(self)))
428 428
429 429 def archive(self, ui, archiver, prefix):
430 430 self._get(self._state + ('hg',))
431 431 abstractsubrepo.archive(self, ui, archiver, prefix)
432 432
433 433 rev = self._state[1]
434 434 ctx = self._repo[rev]
435 435 for subpath in ctx.substate:
436 436 s = subrepo(ctx, subpath)
437 437 s.archive(ui, archiver, os.path.join(prefix, self._path))
438 438
439 439 def dirty(self, ignoreupdate=False):
440 440 r = self._state[1]
441 441 if r == '' and not ignoreupdate: # no state recorded
442 442 return True
443 443 w = self._repo[None]
444 444 if r != w.p1().hex() and not ignoreupdate:
445 445 # different version checked out
446 446 return True
447 447 return w.dirty() # working directory changed
448 448
449 449 def checknested(self, path):
450 450 return self._repo._checknested(self._repo.wjoin(path))
451 451
452 452 def commit(self, text, user, date):
453 453 # don't bother committing in the subrepo if it's only been
454 454 # updated
455 455 if not self.dirty(True):
456 456 return self._repo['.'].hex()
457 457 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
458 458 n = self._repo.commit(text, user, date)
459 459 if not n:
460 460 return self._repo['.'].hex() # different version checked out
461 461 return node.hex(n)
462 462
463 463 def remove(self):
464 464 # we can't fully delete the repository as it may contain
465 465 # local-only history
466 466 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
467 467 hg.clean(self._repo, node.nullid, False)
468 468
469 469 def _get(self, state):
470 470 source, revision, kind = state
471 471 if revision not in self._repo:
472 472 self._repo._subsource = source
473 473 srcurl = _abssource(self._repo)
474 474 other = hg.peer(self._repo.ui, {}, srcurl)
475 475 if len(self._repo) == 0:
476 476 self._repo.ui.status(_('cloning subrepo %s from %s\n')
477 477 % (subrelpath(self), srcurl))
478 478 parentrepo = self._repo._subparent
479 479 shutil.rmtree(self._repo.path)
480 480 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
481 481 self._repo.root, update=False)
482 482 self._initrepo(parentrepo, source, create=True)
483 483 else:
484 484 self._repo.ui.status(_('pulling subrepo %s from %s\n')
485 485 % (subrelpath(self), srcurl))
486 486 self._repo.pull(other)
487 487 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
488 488
489 489 def get(self, state, overwrite=False):
490 490 self._get(state)
491 491 source, revision, kind = state
492 492 self._repo.ui.debug("getting subrepo %s\n" % self._path)
493 493 hg.clean(self._repo, revision, False)
494 494
495 495 def merge(self, state):
496 496 self._get(state)
497 497 cur = self._repo['.']
498 498 dst = self._repo[state[1]]
499 499 anc = dst.ancestor(cur)
500 500
501 501 def mergefunc():
502 502 if anc == cur:
503 503 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
504 504 hg.update(self._repo, state[1])
505 505 elif anc == dst:
506 506 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
507 507 else:
508 508 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
509 509 hg.merge(self._repo, state[1], remind=False)
510 510
511 511 wctx = self._repo[None]
512 512 if self.dirty():
513 513 if anc != dst:
514 514 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
515 515 mergefunc()
516 516 else:
517 517 mergefunc()
518 518 else:
519 519 mergefunc()
520 520
521 521 def push(self, force):
522 522 # push subrepos depth-first for coherent ordering
523 523 c = self._repo['']
524 524 subs = c.substate # only repos that are committed
525 525 for s in sorted(subs):
526 526 if not c.sub(s).push(force):
527 527 return False
528 528
529 529 dsturl = _abssource(self._repo, True)
530 530 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
531 531 (subrelpath(self), dsturl))
532 532 other = hg.peer(self._repo.ui, {}, dsturl)
533 533 return self._repo.push(other, force)
534 534
535 535 def outgoing(self, ui, dest, opts):
536 536 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
537 537
538 538 def incoming(self, ui, source, opts):
539 539 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
540 540
541 541 def files(self):
542 542 rev = self._state[1]
543 543 ctx = self._repo[rev]
544 544 return ctx.manifest()
545 545
546 546 def filedata(self, name):
547 547 rev = self._state[1]
548 548 return self._repo[rev][name].data()
549 549
550 550 def fileflags(self, name):
551 551 rev = self._state[1]
552 552 ctx = self._repo[rev]
553 553 return ctx.flags(name)
554 554
555 555 def walk(self, match):
556 556 ctx = self._repo[None]
557 557 return ctx.walk(match)
558 558
559 559 def forget(self, files):
560 560 ctx = self._repo[None]
561 561 ctx.forget(files)
562 562
563 563 class svnsubrepo(abstractsubrepo):
564 564 def __init__(self, ctx, path, state):
565 565 self._path = path
566 566 self._state = state
567 567 self._ctx = ctx
568 568 self._ui = ctx._repo.ui
569 569 self._exe = util.findexe('svn')
570 570 if not self._exe:
571 571 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
572 572 % self._path)
573 573
574 574 def _svncommand(self, commands, filename='', failok=False):
575 575 cmd = [self._exe]
576 576 extrakw = {}
577 577 if not self._ui.interactive():
578 578 # Making stdin be a pipe should prevent svn from behaving
579 579 # interactively even if we can't pass --non-interactive.
580 580 extrakw['stdin'] = subprocess.PIPE
581 581 # Starting in svn 1.5 --non-interactive is a global flag
582 582 # instead of being per-command, but we need to support 1.4 so
583 583 # we have to be intelligent about what commands take
584 584 # --non-interactive.
585 585 if commands[0] in ('update', 'checkout', 'commit'):
586 586 cmd.append('--non-interactive')
587 587 cmd.extend(commands)
588 588 if filename is not None:
589 589 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
590 590 cmd.append(path)
591 591 env = dict(os.environ)
592 592 # Avoid localized output, preserve current locale for everything else.
593 593 env['LC_MESSAGES'] = 'C'
594 594 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
595 595 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
596 596 universal_newlines=True, env=env, **extrakw)
597 597 stdout, stderr = p.communicate()
598 598 stderr = stderr.strip()
599 599 if not failok:
600 600 if p.returncode:
601 601 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
602 602 if stderr:
603 603 self._ui.warn(stderr + '\n')
604 604 return stdout, stderr
605 605
606 606 @propertycache
607 607 def _svnversion(self):
608 608 output, err = self._svncommand(['--version'], filename=None)
609 609 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
610 610 if not m:
611 611 raise util.Abort(_('cannot retrieve svn tool version'))
612 612 return (int(m.group(1)), int(m.group(2)))
613 613
614 614 def _wcrevs(self):
615 615 # Get the working directory revision as well as the last
616 616 # commit revision so we can compare the subrepo state with
617 617 # both. We used to store the working directory one.
618 618 output, err = self._svncommand(['info', '--xml'])
619 619 doc = xml.dom.minidom.parseString(output)
620 620 entries = doc.getElementsByTagName('entry')
621 621 lastrev, rev = '0', '0'
622 622 if entries:
623 623 rev = str(entries[0].getAttribute('revision')) or '0'
624 624 commits = entries[0].getElementsByTagName('commit')
625 625 if commits:
626 626 lastrev = str(commits[0].getAttribute('revision')) or '0'
627 627 return (lastrev, rev)
628 628
629 629 def _wcrev(self):
630 630 return self._wcrevs()[0]
631 631
632 632 def _wcchanged(self):
633 633 """Return (changes, extchanges) where changes is True
634 634 if the working directory was changed, and extchanges is
635 635 True if any of these changes concern an external entry.
636 636 """
637 637 output, err = self._svncommand(['status', '--xml'])
638 638 externals, changes = [], []
639 639 doc = xml.dom.minidom.parseString(output)
640 640 for e in doc.getElementsByTagName('entry'):
641 641 s = e.getElementsByTagName('wc-status')
642 642 if not s:
643 643 continue
644 644 item = s[0].getAttribute('item')
645 645 props = s[0].getAttribute('props')
646 646 path = e.getAttribute('path')
647 647 if item == 'external':
648 648 externals.append(path)
649 649 if (item not in ('', 'normal', 'unversioned', 'external')
650 650 or props not in ('', 'none', 'normal')):
651 651 changes.append(path)
652 652 for path in changes:
653 653 for ext in externals:
654 654 if path == ext or path.startswith(ext + os.sep):
655 655 return True, True
656 656 return bool(changes), False
657 657
658 658 def dirty(self, ignoreupdate=False):
659 659 if not self._wcchanged()[0]:
660 660 if self._state[1] in self._wcrevs() or ignoreupdate:
661 661 return False
662 662 return True
663 663
664 664 def commit(self, text, user, date):
665 665 # user and date are out of our hands since svn is centralized
666 666 changed, extchanged = self._wcchanged()
667 667 if not changed:
668 668 return self._wcrev()
669 669 if extchanged:
670 670 # Do not try to commit externals
671 671 raise util.Abort(_('cannot commit svn externals'))
672 672 commitinfo, err = self._svncommand(['commit', '-m', text])
673 673 self._ui.status(commitinfo)
674 674 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
675 675 if not newrev:
676 676 raise util.Abort(commitinfo.splitlines()[-1])
677 677 newrev = newrev.groups()[0]
678 678 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
679 679 return newrev
680 680
681 681 def remove(self):
682 682 if self.dirty():
683 683 self._ui.warn(_('not removing repo %s because '
684 684 'it has changes.\n' % self._path))
685 685 return
686 686 self._ui.note(_('removing subrepo %s\n') % self._path)
687 687
688 688 def onerror(function, path, excinfo):
689 689 if function is not os.remove:
690 690 raise
691 691 # read-only files cannot be unlinked under Windows
692 692 s = os.stat(path)
693 693 if (s.st_mode & stat.S_IWRITE) != 0:
694 694 raise
695 695 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
696 696 os.remove(path)
697 697
698 698 path = self._ctx._repo.wjoin(self._path)
699 699 shutil.rmtree(path, onerror=onerror)
700 700 try:
701 701 os.removedirs(os.path.dirname(path))
702 702 except OSError:
703 703 pass
704 704
705 705 def get(self, state, overwrite=False):
706 706 if overwrite:
707 707 self._svncommand(['revert', '--recursive'])
708 708 args = ['checkout']
709 709 if self._svnversion >= (1, 5):
710 710 args.append('--force')
711 711 # The revision must be specified at the end of the URL to properly
712 712 # update to a directory which has since been deleted and recreated.
713 713 args.append('%s@%s' % (state[0], state[1]))
714 714 status, err = self._svncommand(args, failok=True)
715 715 if not re.search('Checked out revision [0-9]+.', status):
716 716 if ('is already a working copy for a different URL' in err
717 717 and (self._wcchanged() == (False, False))):
718 718 # obstructed but clean working copy, so just blow it away.
719 719 self.remove()
720 720 self.get(state, overwrite=False)
721 721 return
722 722 raise util.Abort((status or err).splitlines()[-1])
723 723 self._ui.status(status)
724 724
725 725 def merge(self, state):
726 726 old = self._state[1]
727 727 new = state[1]
728 728 if new != self._wcrev():
729 729 dirty = old == self._wcrev() or self._wcchanged()[0]
730 730 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
731 731 self.get(state, False)
732 732
733 733 def push(self, force):
734 734 # push is a no-op for SVN
735 735 return True
736 736
737 737 def files(self):
738 738 output = self._svncommand(['list'])
739 739 # This works because svn forbids \n in filenames.
740 740 return output.splitlines()
741 741
742 742 def filedata(self, name):
743 743 return self._svncommand(['cat'], name)
744 744
745 745
746 746 class gitsubrepo(abstractsubrepo):
747 747 def __init__(self, ctx, path, state):
748 748 # TODO add git version check.
749 749 self._state = state
750 750 self._ctx = ctx
751 751 self._path = path
752 752 self._relpath = os.path.join(reporelpath(ctx._repo), path)
753 753 self._abspath = ctx._repo.wjoin(path)
754 754 self._subparent = ctx._repo
755 755 self._ui = ctx._repo.ui
756 756
757 757 def _gitcommand(self, commands, env=None, stream=False):
758 758 return self._gitdir(commands, env=env, stream=stream)[0]
759 759
760 760 def _gitdir(self, commands, env=None, stream=False):
761 761 return self._gitnodir(commands, env=env, stream=stream,
762 762 cwd=self._abspath)
763 763
764 764 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
765 765 """Calls the git command
766 766
767 767 The methods tries to call the git command. versions previor to 1.6.0
768 768 are not supported and very probably fail.
769 769 """
770 770 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
771 771 # unless ui.quiet is set, print git's stderr,
772 772 # which is mostly progress and useful info
773 773 errpipe = None
774 774 if self._ui.quiet:
775 775 errpipe = open(os.devnull, 'w')
776 776 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
777 777 close_fds=util.closefds,
778 778 stdout=subprocess.PIPE, stderr=errpipe)
779 779 if stream:
780 780 return p.stdout, None
781 781
782 782 retdata = p.stdout.read().strip()
783 783 # wait for the child to exit to avoid race condition.
784 784 p.wait()
785 785
786 786 if p.returncode != 0 and p.returncode != 1:
787 787 # there are certain error codes that are ok
788 788 command = commands[0]
789 789 if command in ('cat-file', 'symbolic-ref'):
790 790 return retdata, p.returncode
791 791 # for all others, abort
792 792 raise util.Abort('git %s error %d in %s' %
793 793 (command, p.returncode, self._relpath))
794 794
795 795 return retdata, p.returncode
796 796
797 797 def _gitmissing(self):
798 798 return not os.path.exists(os.path.join(self._abspath, '.git'))
799 799
800 800 def _gitstate(self):
801 801 return self._gitcommand(['rev-parse', 'HEAD'])
802 802
803 803 def _gitcurrentbranch(self):
804 804 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
805 805 if err:
806 806 current = None
807 807 return current
808 808
809 809 def _gitremote(self, remote):
810 810 out = self._gitcommand(['remote', 'show', '-n', remote])
811 811 line = out.split('\n')[1]
812 812 i = line.index('URL: ') + len('URL: ')
813 813 return line[i:]
814 814
815 815 def _githavelocally(self, revision):
816 816 out, code = self._gitdir(['cat-file', '-e', revision])
817 817 return code == 0
818 818
819 819 def _gitisancestor(self, r1, r2):
820 820 base = self._gitcommand(['merge-base', r1, r2])
821 821 return base == r1
822 822
823 823 def _gitisbare(self):
824 824 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
825 825
826 826 def _gitbranchmap(self):
827 827 '''returns 2 things:
828 828 a map from git branch to revision
829 829 a map from revision to branches'''
830 830 branch2rev = {}
831 831 rev2branch = {}
832 832
833 833 out = self._gitcommand(['for-each-ref', '--format',
834 834 '%(objectname) %(refname)'])
835 835 for line in out.split('\n'):
836 836 revision, ref = line.split(' ')
837 837 if (not ref.startswith('refs/heads/') and
838 838 not ref.startswith('refs/remotes/')):
839 839 continue
840 840 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
841 841 continue # ignore remote/HEAD redirects
842 842 branch2rev[ref] = revision
843 843 rev2branch.setdefault(revision, []).append(ref)
844 844 return branch2rev, rev2branch
845 845
846 846 def _gittracking(self, branches):
847 847 'return map of remote branch to local tracking branch'
848 848 # assumes no more than one local tracking branch for each remote
849 849 tracking = {}
850 850 for b in branches:
851 851 if b.startswith('refs/remotes/'):
852 852 continue
853 853 bname = b.split('/', 2)[2]
854 854 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
855 855 if remote:
856 856 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
857 857 tracking['refs/remotes/%s/%s' %
858 858 (remote, ref.split('/', 2)[2])] = b
859 859 return tracking
860 860
861 861 def _abssource(self, source):
862 862 if '://' not in source:
863 863 # recognize the scp syntax as an absolute source
864 864 colon = source.find(':')
865 865 if colon != -1 and '/' not in source[:colon]:
866 866 return source
867 867 self._subsource = source
868 868 return _abssource(self)
869 869
870 870 def _fetch(self, source, revision):
871 871 if self._gitmissing():
872 872 source = self._abssource(source)
873 873 self._ui.status(_('cloning subrepo %s from %s\n') %
874 874 (self._relpath, source))
875 875 self._gitnodir(['clone', source, self._abspath])
876 876 if self._githavelocally(revision):
877 877 return
878 878 self._ui.status(_('pulling subrepo %s from %s\n') %
879 879 (self._relpath, self._gitremote('origin')))
880 880 # try only origin: the originally cloned repo
881 881 self._gitcommand(['fetch'])
882 882 if not self._githavelocally(revision):
883 883 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
884 884 (revision, self._relpath))
885 885
886 886 def dirty(self, ignoreupdate=False):
887 887 if self._gitmissing():
888 888 return self._state[1] != ''
889 889 if self._gitisbare():
890 890 return True
891 891 if not ignoreupdate and self._state[1] != self._gitstate():
892 892 # different version checked out
893 893 return True
894 894 # check for staged changes or modified files; ignore untracked files
895 895 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
896 896 return code == 1
897 897
898 898 def get(self, state, overwrite=False):
899 899 source, revision, kind = state
900 900 if not revision:
901 901 self.remove()
902 902 return
903 903 self._fetch(source, revision)
904 904 # if the repo was set to be bare, unbare it
905 905 if self._gitisbare():
906 906 self._gitcommand(['config', 'core.bare', 'false'])
907 907 if self._gitstate() == revision:
908 908 self._gitcommand(['reset', '--hard', 'HEAD'])
909 909 return
910 910 elif self._gitstate() == revision:
911 911 if overwrite:
912 912 # first reset the index to unmark new files for commit, because
913 913 # reset --hard will otherwise throw away files added for commit,
914 914 # not just unmark them.
915 915 self._gitcommand(['reset', 'HEAD'])
916 916 self._gitcommand(['reset', '--hard', 'HEAD'])
917 917 return
918 918 branch2rev, rev2branch = self._gitbranchmap()
919 919
920 920 def checkout(args):
921 921 cmd = ['checkout']
922 922 if overwrite:
923 923 # first reset the index to unmark new files for commit, because
924 924 # the -f option will otherwise throw away files added for
925 925 # commit, not just unmark them.
926 926 self._gitcommand(['reset', 'HEAD'])
927 927 cmd.append('-f')
928 928 self._gitcommand(cmd + args)
929 929
930 930 def rawcheckout():
931 931 # no branch to checkout, check it out with no branch
932 932 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
933 933 self._relpath)
934 934 self._ui.warn(_('check out a git branch if you intend '
935 935 'to make changes\n'))
936 936 checkout(['-q', revision])
937 937
938 938 if revision not in rev2branch:
939 939 rawcheckout()
940 940 return
941 941 branches = rev2branch[revision]
942 942 firstlocalbranch = None
943 943 for b in branches:
944 944 if b == 'refs/heads/master':
945 945 # master trumps all other branches
946 946 checkout(['refs/heads/master'])
947 947 return
948 948 if not firstlocalbranch and not b.startswith('refs/remotes/'):
949 949 firstlocalbranch = b
950 950 if firstlocalbranch:
951 951 checkout([firstlocalbranch])
952 952 return
953 953
954 954 tracking = self._gittracking(branch2rev.keys())
955 955 # choose a remote branch already tracked if possible
956 956 remote = branches[0]
957 957 if remote not in tracking:
958 958 for b in branches:
959 959 if b in tracking:
960 960 remote = b
961 961 break
962 962
963 963 if remote not in tracking:
964 964 # create a new local tracking branch
965 965 local = remote.split('/', 2)[2]
966 966 checkout(['-b', local, remote])
967 967 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
968 968 # When updating to a tracked remote branch,
969 969 # if the local tracking branch is downstream of it,
970 970 # a normal `git pull` would have performed a "fast-forward merge"
971 971 # which is equivalent to updating the local branch to the remote.
972 972 # Since we are only looking at branching at update, we need to
973 973 # detect this situation and perform this action lazily.
974 974 if tracking[remote] != self._gitcurrentbranch():
975 975 checkout([tracking[remote]])
976 976 self._gitcommand(['merge', '--ff', remote])
977 977 else:
978 978 # a real merge would be required, just checkout the revision
979 979 rawcheckout()
980 980
981 981 def commit(self, text, user, date):
982 982 if self._gitmissing():
983 983 raise util.Abort(_("subrepo %s is missing") % self._relpath)
984 984 cmd = ['commit', '-a', '-m', text]
985 985 env = os.environ.copy()
986 986 if user:
987 987 cmd += ['--author', user]
988 988 if date:
989 989 # git's date parser silently ignores when seconds < 1e9
990 990 # convert to ISO8601
991 991 env['GIT_AUTHOR_DATE'] = util.datestr(date,
992 992 '%Y-%m-%dT%H:%M:%S %1%2')
993 993 self._gitcommand(cmd, env=env)
994 994 # make sure commit works otherwise HEAD might not exist under certain
995 995 # circumstances
996 996 return self._gitstate()
997 997
998 998 def merge(self, state):
999 999 source, revision, kind = state
1000 1000 self._fetch(source, revision)
1001 1001 base = self._gitcommand(['merge-base', revision, self._state[1]])
1002 1002 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1003 1003
1004 1004 def mergefunc():
1005 1005 if base == revision:
1006 1006 self.get(state) # fast forward merge
1007 1007 elif base != self._state[1]:
1008 1008 self._gitcommand(['merge', '--no-commit', revision])
1009 1009
1010 1010 if self.dirty():
1011 1011 if self._gitstate() != revision:
1012 1012 dirty = self._gitstate() == self._state[1] or code != 0
1013 1013 if _updateprompt(self._ui, self, dirty,
1014 1014 self._state[1][:7], revision[:7]):
1015 1015 mergefunc()
1016 1016 else:
1017 1017 mergefunc()
1018 1018
1019 1019 def push(self, force):
1020 1020 if not self._state[1]:
1021 1021 return True
1022 1022 if self._gitmissing():
1023 1023 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1024 1024 # if a branch in origin contains the revision, nothing to do
1025 1025 branch2rev, rev2branch = self._gitbranchmap()
1026 1026 if self._state[1] in rev2branch:
1027 1027 for b in rev2branch[self._state[1]]:
1028 1028 if b.startswith('refs/remotes/origin/'):
1029 1029 return True
1030 1030 for b, revision in branch2rev.iteritems():
1031 1031 if b.startswith('refs/remotes/origin/'):
1032 1032 if self._gitisancestor(self._state[1], revision):
1033 1033 return True
1034 1034 # otherwise, try to push the currently checked out branch
1035 1035 cmd = ['push']
1036 1036 if force:
1037 1037 cmd.append('--force')
1038 1038
1039 1039 current = self._gitcurrentbranch()
1040 1040 if current:
1041 1041 # determine if the current branch is even useful
1042 1042 if not self._gitisancestor(self._state[1], current):
1043 1043 self._ui.warn(_('unrelated git branch checked out '
1044 1044 'in subrepo %s\n') % self._relpath)
1045 1045 return False
1046 1046 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1047 1047 (current.split('/', 2)[2], self._relpath))
1048 1048 self._gitcommand(cmd + ['origin', current])
1049 1049 return True
1050 1050 else:
1051 1051 self._ui.warn(_('no branch checked out in subrepo %s\n'
1052 1052 'cannot push revision %s') %
1053 1053 (self._relpath, self._state[1]))
1054 1054 return False
1055 1055
1056 1056 def remove(self):
1057 1057 if self._gitmissing():
1058 1058 return
1059 1059 if self.dirty():
1060 1060 self._ui.warn(_('not removing repo %s because '
1061 1061 'it has changes.\n') % self._relpath)
1062 1062 return
1063 1063 # we can't fully delete the repository as it may contain
1064 1064 # local-only history
1065 1065 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1066 1066 self._gitcommand(['config', 'core.bare', 'true'])
1067 1067 for f in os.listdir(self._abspath):
1068 1068 if f == '.git':
1069 1069 continue
1070 1070 path = os.path.join(self._abspath, f)
1071 1071 if os.path.isdir(path) and not os.path.islink(path):
1072 1072 shutil.rmtree(path)
1073 1073 else:
1074 1074 os.remove(path)
1075 1075
1076 1076 def archive(self, ui, archiver, prefix):
1077 1077 source, revision = self._state
1078 1078 if not revision:
1079 1079 return
1080 1080 self._fetch(source, revision)
1081 1081
1082 1082 # Parse git's native archive command.
1083 1083 # This should be much faster than manually traversing the trees
1084 1084 # and objects with many subprocess calls.
1085 1085 tarstream = self._gitcommand(['archive', revision], stream=True)
1086 1086 tar = tarfile.open(fileobj=tarstream, mode='r|')
1087 1087 relpath = subrelpath(self)
1088 1088 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1089 1089 for i, info in enumerate(tar):
1090 1090 if info.isdir():
1091 1091 continue
1092 1092 if info.issym():
1093 1093 data = info.linkname
1094 1094 else:
1095 1095 data = tar.extractfile(info).read()
1096 1096 archiver.addfile(os.path.join(prefix, self._path, info.name),
1097 1097 info.mode, info.issym(), data)
1098 1098 ui.progress(_('archiving (%s)') % relpath, i + 1,
1099 1099 unit=_('files'))
1100 1100 ui.progress(_('archiving (%s)') % relpath, None)
1101 1101
1102 1102
1103 1103 def status(self, rev2, **opts):
1104 1104 rev1 = self._state[1]
1105 1105 if self._gitmissing() or not rev1:
1106 1106 # if the repo is missing, return no results
1107 1107 return [], [], [], [], [], [], []
1108 1108 modified, added, removed = [], [], []
1109 1109 if rev2:
1110 1110 command = ['diff-tree', rev1, rev2]
1111 1111 else:
1112 1112 command = ['diff-index', rev1]
1113 1113 out = self._gitcommand(command)
1114 1114 for line in out.split('\n'):
1115 1115 tab = line.find('\t')
1116 1116 if tab == -1:
1117 1117 continue
1118 1118 status, f = line[tab - 1], line[tab + 1:]
1119 1119 if status == 'M':
1120 1120 modified.append(f)
1121 1121 elif status == 'A':
1122 1122 added.append(f)
1123 1123 elif status == 'D':
1124 1124 removed.append(f)
1125 1125
1126 1126 deleted = unknown = ignored = clean = []
1127 1127 return modified, added, removed, deleted, unknown, ignored, clean
1128 1128
1129 1129 types = {
1130 1130 'hg': hgsubrepo,
1131 1131 'svn': svnsubrepo,
1132 1132 'git': gitsubrepo,
1133 1133 }
@@ -1,1742 +1,1741 b''
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, signal
19 import os, time, datetime, calendar, textwrap, signal
20 20 import imp, socket, urllib
21 21
22 22 if os.name == 'nt':
23 23 import windows as platform
24 24 else:
25 25 import posix as platform
26 26
27 27 cachestat = platform.cachestat
28 28 checkexec = platform.checkexec
29 29 checklink = platform.checklink
30 30 copymode = platform.copymode
31 31 executablepath = platform.executablepath
32 32 expandglobs = platform.expandglobs
33 33 explainexit = platform.explainexit
34 34 findexe = platform.findexe
35 35 gethgcmd = platform.gethgcmd
36 36 getuser = platform.getuser
37 37 groupmembers = platform.groupmembers
38 38 groupname = platform.groupname
39 39 hidewindow = platform.hidewindow
40 40 isexec = platform.isexec
41 41 isowner = platform.isowner
42 42 localpath = platform.localpath
43 43 lookupreg = platform.lookupreg
44 44 makedir = platform.makedir
45 45 nlinks = platform.nlinks
46 46 normpath = platform.normpath
47 47 normcase = platform.normcase
48 48 nulldev = platform.nulldev
49 49 openhardlinks = platform.openhardlinks
50 50 oslink = platform.oslink
51 51 parsepatchoutput = platform.parsepatchoutput
52 52 pconvert = platform.pconvert
53 53 popen = platform.popen
54 54 posixfile = platform.posixfile
55 55 quotecommand = platform.quotecommand
56 56 realpath = platform.realpath
57 57 rename = platform.rename
58 58 samedevice = platform.samedevice
59 59 samefile = platform.samefile
60 60 samestat = platform.samestat
61 61 setbinary = platform.setbinary
62 62 setflags = platform.setflags
63 63 setsignalhandler = platform.setsignalhandler
64 64 shellquote = platform.shellquote
65 65 spawndetached = platform.spawndetached
66 66 sshargs = platform.sshargs
67 67 statfiles = platform.statfiles
68 68 termwidth = platform.termwidth
69 69 testpid = platform.testpid
70 70 umask = platform.umask
71 71 unlink = platform.unlink
72 72 unlinkpath = platform.unlinkpath
73 73 username = platform.username
74 74
75 75 # Python compatibility
76 76
77 77 def sha1(s=''):
78 78 '''
79 79 Low-overhead wrapper around Python's SHA support
80 80
81 81 >>> f = _fastsha1
82 82 >>> a = sha1()
83 83 >>> a = f()
84 84 >>> a.hexdigest()
85 85 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
86 86 '''
87 87
88 88 return _fastsha1(s)
89 89
90 90 _notset = object()
91 91 def safehasattr(thing, attr):
92 92 return getattr(thing, attr, _notset) is not _notset
93 93
94 94 def _fastsha1(s=''):
95 95 # This function will import sha1 from hashlib or sha (whichever is
96 96 # available) and overwrite itself with it on the first call.
97 97 # Subsequent calls will go directly to the imported function.
98 98 if sys.version_info >= (2, 5):
99 99 from hashlib import sha1 as _sha1
100 100 else:
101 101 from sha import sha as _sha1
102 102 global _fastsha1, sha1
103 103 _fastsha1 = sha1 = _sha1
104 104 return _sha1(s)
105 105
106 106 import __builtin__
107 107
108 108 if sys.version_info[0] < 3:
109 109 def fakebuffer(sliceable, offset=0):
110 110 return sliceable[offset:]
111 111 else:
112 112 def fakebuffer(sliceable, offset=0):
113 113 return memoryview(sliceable)[offset:]
114 114 try:
115 115 buffer
116 116 except NameError:
117 117 __builtin__.buffer = fakebuffer
118 118
119 119 import subprocess
120 120 closefds = os.name == 'posix'
121 121
122 122 def popen2(cmd, env=None, newlines=False):
123 123 # Setting bufsize to -1 lets the system decide the buffer size.
124 124 # The default for bufsize is 0, meaning unbuffered. This leads to
125 125 # poor performance on Mac OS X: http://bugs.python.org/issue4194
126 126 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
127 127 close_fds=closefds,
128 128 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
129 129 universal_newlines=newlines,
130 130 env=env)
131 131 return p.stdin, p.stdout
132 132
133 133 def popen3(cmd, env=None, newlines=False):
134 134 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
135 135 close_fds=closefds,
136 136 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
137 137 stderr=subprocess.PIPE,
138 138 universal_newlines=newlines,
139 139 env=env)
140 140 return p.stdin, p.stdout, p.stderr
141 141
142 142 def version():
143 143 """Return version information if available."""
144 144 try:
145 145 import __version__
146 146 return __version__.version
147 147 except ImportError:
148 148 return 'unknown'
149 149
150 150 # used by parsedate
151 151 defaultdateformats = (
152 152 '%Y-%m-%d %H:%M:%S',
153 153 '%Y-%m-%d %I:%M:%S%p',
154 154 '%Y-%m-%d %H:%M',
155 155 '%Y-%m-%d %I:%M%p',
156 156 '%Y-%m-%d',
157 157 '%m-%d',
158 158 '%m/%d',
159 159 '%m/%d/%y',
160 160 '%m/%d/%Y',
161 161 '%a %b %d %H:%M:%S %Y',
162 162 '%a %b %d %I:%M:%S%p %Y',
163 163 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
164 164 '%b %d %H:%M:%S %Y',
165 165 '%b %d %I:%M:%S%p %Y',
166 166 '%b %d %H:%M:%S',
167 167 '%b %d %I:%M:%S%p',
168 168 '%b %d %H:%M',
169 169 '%b %d %I:%M%p',
170 170 '%b %d %Y',
171 171 '%b %d',
172 172 '%H:%M:%S',
173 173 '%I:%M:%S%p',
174 174 '%H:%M',
175 175 '%I:%M%p',
176 176 )
177 177
178 178 extendeddateformats = defaultdateformats + (
179 179 "%Y",
180 180 "%Y-%m",
181 181 "%b",
182 182 "%b %Y",
183 183 )
184 184
185 185 def cachefunc(func):
186 186 '''cache the result of function calls'''
187 187 # XXX doesn't handle keywords args
188 188 cache = {}
189 189 if func.func_code.co_argcount == 1:
190 190 # we gain a small amount of time because
191 191 # we don't need to pack/unpack the list
192 192 def f(arg):
193 193 if arg not in cache:
194 194 cache[arg] = func(arg)
195 195 return cache[arg]
196 196 else:
197 197 def f(*args):
198 198 if args not in cache:
199 199 cache[args] = func(*args)
200 200 return cache[args]
201 201
202 202 return f
203 203
204 204 def lrucachefunc(func):
205 205 '''cache most recent results of function calls'''
206 206 cache = {}
207 207 order = []
208 208 if func.func_code.co_argcount == 1:
209 209 def f(arg):
210 210 if arg not in cache:
211 211 if len(cache) > 20:
212 212 del cache[order.pop(0)]
213 213 cache[arg] = func(arg)
214 214 else:
215 215 order.remove(arg)
216 216 order.append(arg)
217 217 return cache[arg]
218 218 else:
219 219 def f(*args):
220 220 if args not in cache:
221 221 if len(cache) > 20:
222 222 del cache[order.pop(0)]
223 223 cache[args] = func(*args)
224 224 else:
225 225 order.remove(args)
226 226 order.append(args)
227 227 return cache[args]
228 228
229 229 return f
230 230
231 231 class propertycache(object):
232 232 def __init__(self, func):
233 233 self.func = func
234 234 self.name = func.__name__
235 235 def __get__(self, obj, type=None):
236 236 result = self.func(obj)
237 237 setattr(obj, self.name, result)
238 238 return result
239 239
240 240 def pipefilter(s, cmd):
241 241 '''filter string S through command CMD, returning its output'''
242 242 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
243 243 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
244 244 pout, perr = p.communicate(s)
245 245 return pout
246 246
247 247 def tempfilter(s, cmd):
248 248 '''filter string S through a pair of temporary files with CMD.
249 249 CMD is used as a template to create the real command to be run,
250 250 with the strings INFILE and OUTFILE replaced by the real names of
251 251 the temporary files generated.'''
252 252 inname, outname = None, None
253 253 try:
254 254 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
255 255 fp = os.fdopen(infd, 'wb')
256 256 fp.write(s)
257 257 fp.close()
258 258 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
259 259 os.close(outfd)
260 260 cmd = cmd.replace('INFILE', inname)
261 261 cmd = cmd.replace('OUTFILE', outname)
262 262 code = os.system(cmd)
263 263 if sys.platform == 'OpenVMS' and code & 1:
264 264 code = 0
265 265 if code:
266 266 raise Abort(_("command '%s' failed: %s") %
267 267 (cmd, explainexit(code)))
268 268 fp = open(outname, 'rb')
269 269 r = fp.read()
270 270 fp.close()
271 271 return r
272 272 finally:
273 273 try:
274 274 if inname:
275 275 os.unlink(inname)
276 276 except OSError:
277 277 pass
278 278 try:
279 279 if outname:
280 280 os.unlink(outname)
281 281 except OSError:
282 282 pass
283 283
284 284 filtertable = {
285 285 'tempfile:': tempfilter,
286 286 'pipe:': pipefilter,
287 287 }
288 288
289 289 def filter(s, cmd):
290 290 "filter a string through a command that transforms its input to its output"
291 291 for name, fn in filtertable.iteritems():
292 292 if cmd.startswith(name):
293 293 return fn(s, cmd[len(name):].lstrip())
294 294 return pipefilter(s, cmd)
295 295
296 296 def binary(s):
297 297 """return true if a string is binary data"""
298 298 return bool(s and '\0' in s)
299 299
300 300 def increasingchunks(source, min=1024, max=65536):
301 301 '''return no less than min bytes per chunk while data remains,
302 302 doubling min after each chunk until it reaches max'''
303 303 def log2(x):
304 304 if not x:
305 305 return 0
306 306 i = 0
307 307 while x:
308 308 x >>= 1
309 309 i += 1
310 310 return i - 1
311 311
312 312 buf = []
313 313 blen = 0
314 314 for chunk in source:
315 315 buf.append(chunk)
316 316 blen += len(chunk)
317 317 if blen >= min:
318 318 if min < max:
319 319 min = min << 1
320 320 nmin = 1 << log2(blen)
321 321 if nmin > min:
322 322 min = nmin
323 323 if min > max:
324 324 min = max
325 325 yield ''.join(buf)
326 326 blen = 0
327 327 buf = []
328 328 if buf:
329 329 yield ''.join(buf)
330 330
331 331 Abort = error.Abort
332 332
333 333 def always(fn):
334 334 return True
335 335
336 336 def never(fn):
337 337 return False
338 338
339 339 def pathto(root, n1, n2):
340 340 '''return the relative path from one place to another.
341 341 root should use os.sep to separate directories
342 342 n1 should use os.sep to separate directories
343 343 n2 should use "/" to separate directories
344 344 returns an os.sep-separated path.
345 345
346 346 If n1 is a relative path, it's assumed it's
347 347 relative to root.
348 348 n2 should always be relative to root.
349 349 '''
350 350 if not n1:
351 351 return localpath(n2)
352 352 if os.path.isabs(n1):
353 353 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
354 354 return os.path.join(root, localpath(n2))
355 355 n2 = '/'.join((pconvert(root), n2))
356 356 a, b = splitpath(n1), n2.split('/')
357 357 a.reverse()
358 358 b.reverse()
359 359 while a and b and a[-1] == b[-1]:
360 360 a.pop()
361 361 b.pop()
362 362 b.reverse()
363 363 return os.sep.join((['..'] * len(a)) + b) or '.'
364 364
365 365 _hgexecutable = None
366 366
367 367 def mainfrozen():
368 368 """return True if we are a frozen executable.
369 369
370 370 The code supports py2exe (most common, Windows only) and tools/freeze
371 371 (portable, not much used).
372 372 """
373 373 return (safehasattr(sys, "frozen") or # new py2exe
374 374 safehasattr(sys, "importers") or # old py2exe
375 375 imp.is_frozen("__main__")) # tools/freeze
376 376
377 377 def hgexecutable():
378 378 """return location of the 'hg' executable.
379 379
380 380 Defaults to $HG or 'hg' in the search path.
381 381 """
382 382 if _hgexecutable is None:
383 383 hg = os.environ.get('HG')
384 384 mainmod = sys.modules['__main__']
385 385 if hg:
386 386 _sethgexecutable(hg)
387 387 elif mainfrozen():
388 388 _sethgexecutable(sys.executable)
389 389 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
390 390 _sethgexecutable(mainmod.__file__)
391 391 else:
392 392 exe = findexe('hg') or os.path.basename(sys.argv[0])
393 393 _sethgexecutable(exe)
394 394 return _hgexecutable
395 395
396 396 def _sethgexecutable(path):
397 397 """set location of the 'hg' executable"""
398 398 global _hgexecutable
399 399 _hgexecutable = path
400 400
401 401 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
402 402 '''enhanced shell command execution.
403 403 run with environment maybe modified, maybe in different dir.
404 404
405 405 if command fails and onerr is None, return status. if ui object,
406 406 print error message and return status, else raise onerr object as
407 407 exception.
408 408
409 409 if out is specified, it is assumed to be a file-like object that has a
410 410 write() method. stdout and stderr will be redirected to out.'''
411 411 try:
412 412 sys.stdout.flush()
413 413 except Exception:
414 414 pass
415 415 def py2shell(val):
416 416 'convert python object into string that is useful to shell'
417 417 if val is None or val is False:
418 418 return '0'
419 419 if val is True:
420 420 return '1'
421 421 return str(val)
422 422 origcmd = cmd
423 423 cmd = quotecommand(cmd)
424 424 env = dict(os.environ)
425 425 env.update((k, py2shell(v)) for k, v in environ.iteritems())
426 426 env['HG'] = hgexecutable()
427 427 if out is None or out == sys.__stdout__:
428 428 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
429 429 env=env, cwd=cwd)
430 430 else:
431 431 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
432 432 env=env, cwd=cwd, stdout=subprocess.PIPE,
433 433 stderr=subprocess.STDOUT)
434 434 for line in proc.stdout:
435 435 out.write(line)
436 436 proc.wait()
437 437 rc = proc.returncode
438 438 if sys.platform == 'OpenVMS' and rc & 1:
439 439 rc = 0
440 440 if rc and onerr:
441 441 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
442 442 explainexit(rc)[0])
443 443 if errprefix:
444 444 errmsg = '%s: %s' % (errprefix, errmsg)
445 445 try:
446 446 onerr.warn(errmsg + '\n')
447 447 except AttributeError:
448 448 raise onerr(errmsg)
449 449 return rc
450 450
451 451 def checksignature(func):
452 452 '''wrap a function with code to check for calling errors'''
453 453 def check(*args, **kwargs):
454 454 try:
455 455 return func(*args, **kwargs)
456 456 except TypeError:
457 457 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
458 458 raise error.SignatureError
459 459 raise
460 460
461 461 return check
462 462
463 463 def copyfile(src, dest):
464 464 "copy a file, preserving mode and atime/mtime"
465 465 if os.path.islink(src):
466 466 try:
467 467 os.unlink(dest)
468 468 except OSError:
469 469 pass
470 470 os.symlink(os.readlink(src), dest)
471 471 else:
472 472 try:
473 473 shutil.copyfile(src, dest)
474 474 shutil.copymode(src, dest)
475 475 except shutil.Error, inst:
476 476 raise Abort(str(inst))
477 477
478 478 def copyfiles(src, dst, hardlink=None):
479 479 """Copy a directory tree using hardlinks if possible"""
480 480
481 481 if hardlink is None:
482 482 hardlink = (os.stat(src).st_dev ==
483 483 os.stat(os.path.dirname(dst)).st_dev)
484 484
485 485 num = 0
486 486 if os.path.isdir(src):
487 487 os.mkdir(dst)
488 488 for name, kind in osutil.listdir(src):
489 489 srcname = os.path.join(src, name)
490 490 dstname = os.path.join(dst, name)
491 491 hardlink, n = copyfiles(srcname, dstname, hardlink)
492 492 num += n
493 493 else:
494 494 if hardlink:
495 495 try:
496 496 oslink(src, dst)
497 497 except (IOError, OSError):
498 498 hardlink = False
499 499 shutil.copy(src, dst)
500 500 else:
501 501 shutil.copy(src, dst)
502 502 num += 1
503 503
504 504 return hardlink, num
505 505
506 506 _winreservednames = '''con prn aux nul
507 507 com1 com2 com3 com4 com5 com6 com7 com8 com9
508 508 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
509 509 _winreservedchars = ':*?"<>|'
510 510 def checkwinfilename(path):
511 511 '''Check that the base-relative path is a valid filename on Windows.
512 512 Returns None if the path is ok, or a UI string describing the problem.
513 513
514 514 >>> checkwinfilename("just/a/normal/path")
515 515 >>> checkwinfilename("foo/bar/con.xml")
516 516 "filename contains 'con', which is reserved on Windows"
517 517 >>> checkwinfilename("foo/con.xml/bar")
518 518 "filename contains 'con', which is reserved on Windows"
519 519 >>> checkwinfilename("foo/bar/xml.con")
520 520 >>> checkwinfilename("foo/bar/AUX/bla.txt")
521 521 "filename contains 'AUX', which is reserved on Windows"
522 522 >>> checkwinfilename("foo/bar/bla:.txt")
523 523 "filename contains ':', which is reserved on Windows"
524 524 >>> checkwinfilename("foo/bar/b\07la.txt")
525 525 "filename contains '\\\\x07', which is invalid on Windows"
526 526 >>> checkwinfilename("foo/bar/bla ")
527 527 "filename ends with ' ', which is not allowed on Windows"
528 528 >>> checkwinfilename("../bar")
529 529 '''
530 530 for n in path.replace('\\', '/').split('/'):
531 531 if not n:
532 532 continue
533 533 for c in n:
534 534 if c in _winreservedchars:
535 535 return _("filename contains '%s', which is reserved "
536 536 "on Windows") % c
537 537 if ord(c) <= 31:
538 538 return _("filename contains %r, which is invalid "
539 539 "on Windows") % c
540 540 base = n.split('.')[0]
541 541 if base and base.lower() in _winreservednames:
542 542 return _("filename contains '%s', which is reserved "
543 543 "on Windows") % base
544 544 t = n[-1]
545 545 if t in '. ' and n not in '..':
546 546 return _("filename ends with '%s', which is not allowed "
547 547 "on Windows") % t
548 548
549 549 if os.name == 'nt':
550 550 checkosfilename = checkwinfilename
551 551 else:
552 552 checkosfilename = platform.checkosfilename
553 553
554 554 def makelock(info, pathname):
555 555 try:
556 556 return os.symlink(info, pathname)
557 557 except OSError, why:
558 558 if why.errno == errno.EEXIST:
559 559 raise
560 560 except AttributeError: # no symlink in os
561 561 pass
562 562
563 563 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
564 564 os.write(ld, info)
565 565 os.close(ld)
566 566
567 567 def readlock(pathname):
568 568 try:
569 569 return os.readlink(pathname)
570 570 except OSError, why:
571 571 if why.errno not in (errno.EINVAL, errno.ENOSYS):
572 572 raise
573 573 except AttributeError: # no symlink in os
574 574 pass
575 575 fp = posixfile(pathname)
576 576 r = fp.read()
577 577 fp.close()
578 578 return r
579 579
580 580 def fstat(fp):
581 581 '''stat file object that may not have fileno method.'''
582 582 try:
583 583 return os.fstat(fp.fileno())
584 584 except AttributeError:
585 585 return os.stat(fp.name)
586 586
587 587 # File system features
588 588
589 589 def checkcase(path):
590 590 """
591 591 Check whether the given path is on a case-sensitive filesystem
592 592
593 593 Requires a path (like /foo/.hg) ending with a foldable final
594 594 directory component.
595 595 """
596 596 s1 = os.stat(path)
597 597 d, b = os.path.split(path)
598 598 p2 = os.path.join(d, b.upper())
599 599 if path == p2:
600 600 p2 = os.path.join(d, b.lower())
601 601 try:
602 602 s2 = os.stat(p2)
603 603 if s2 == s1:
604 604 return False
605 605 return True
606 606 except OSError:
607 607 return True
608 608
609 609 _fspathcache = {}
610 610 def fspath(name, root):
611 611 '''Get name in the case stored in the filesystem
612 612
613 613 The name is either relative to root, or it is an absolute path starting
614 614 with root. Note that this function is unnecessary, and should not be
615 615 called, for case-sensitive filesystems (simply because it's expensive).
616 616 '''
617 617 # If name is absolute, make it relative
618 618 if name.lower().startswith(root.lower()):
619 619 l = len(root)
620 620 if name[l] == os.sep or name[l] == os.altsep:
621 621 l = l + 1
622 622 name = name[l:]
623 623
624 624 if not os.path.lexists(os.path.join(root, name)):
625 625 return None
626 626
627 627 seps = os.sep
628 628 if os.altsep:
629 629 seps = seps + os.altsep
630 630 # Protect backslashes. This gets silly very quickly.
631 631 seps.replace('\\','\\\\')
632 632 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
633 633 dir = os.path.normcase(os.path.normpath(root))
634 634 result = []
635 635 for part, sep in pattern.findall(name):
636 636 if sep:
637 637 result.append(sep)
638 638 continue
639 639
640 640 if dir not in _fspathcache:
641 641 _fspathcache[dir] = os.listdir(dir)
642 642 contents = _fspathcache[dir]
643 643
644 644 lpart = part.lower()
645 645 lenp = len(part)
646 646 for n in contents:
647 647 if lenp == len(n) and n.lower() == lpart:
648 648 result.append(n)
649 649 break
650 650 else:
651 651 # Cannot happen, as the file exists!
652 652 result.append(part)
653 653 dir = os.path.join(dir, lpart)
654 654
655 655 return ''.join(result)
656 656
657 657 def checknlink(testfile):
658 658 '''check whether hardlink count reporting works properly'''
659 659
660 660 # testfile may be open, so we need a separate file for checking to
661 661 # work around issue2543 (or testfile may get lost on Samba shares)
662 662 f1 = testfile + ".hgtmp1"
663 663 if os.path.lexists(f1):
664 664 return False
665 665 try:
666 666 posixfile(f1, 'w').close()
667 667 except IOError:
668 668 return False
669 669
670 670 f2 = testfile + ".hgtmp2"
671 671 fd = None
672 672 try:
673 673 try:
674 674 oslink(f1, f2)
675 675 except OSError:
676 676 return False
677 677
678 678 # nlinks() may behave differently for files on Windows shares if
679 679 # the file is open.
680 680 fd = posixfile(f2)
681 681 return nlinks(f2) > 1
682 682 finally:
683 683 if fd is not None:
684 684 fd.close()
685 685 for f in (f1, f2):
686 686 try:
687 687 os.unlink(f)
688 688 except OSError:
689 689 pass
690 690
691 691 return False
692 692
693 693 def endswithsep(path):
694 694 '''Check path ends with os.sep or os.altsep.'''
695 695 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
696 696
697 697 def splitpath(path):
698 698 '''Split path by os.sep.
699 699 Note that this function does not use os.altsep because this is
700 700 an alternative of simple "xxx.split(os.sep)".
701 701 It is recommended to use os.path.normpath() before using this
702 702 function if need.'''
703 703 return path.split(os.sep)
704 704
705 705 def gui():
706 706 '''Are we running in a GUI?'''
707 707 if sys.platform == 'darwin':
708 708 if 'SSH_CONNECTION' in os.environ:
709 709 # handle SSH access to a box where the user is logged in
710 710 return False
711 711 elif getattr(osutil, 'isgui', None):
712 712 # check if a CoreGraphics session is available
713 713 return osutil.isgui()
714 714 else:
715 715 # pure build; use a safe default
716 716 return True
717 717 else:
718 718 return os.name == "nt" or os.environ.get("DISPLAY")
719 719
720 720 def mktempcopy(name, emptyok=False, createmode=None):
721 721 """Create a temporary file with the same contents from name
722 722
723 723 The permission bits are copied from the original file.
724 724
725 725 If the temporary file is going to be truncated immediately, you
726 726 can use emptyok=True as an optimization.
727 727
728 728 Returns the name of the temporary file.
729 729 """
730 730 d, fn = os.path.split(name)
731 731 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
732 732 os.close(fd)
733 733 # Temporary files are created with mode 0600, which is usually not
734 734 # what we want. If the original file already exists, just copy
735 735 # its mode. Otherwise, manually obey umask.
736 736 copymode(name, temp, createmode)
737 737 if emptyok:
738 738 return temp
739 739 try:
740 740 try:
741 741 ifp = posixfile(name, "rb")
742 742 except IOError, inst:
743 743 if inst.errno == errno.ENOENT:
744 744 return temp
745 745 if not getattr(inst, 'filename', None):
746 746 inst.filename = name
747 747 raise
748 748 ofp = posixfile(temp, "wb")
749 749 for chunk in filechunkiter(ifp):
750 750 ofp.write(chunk)
751 751 ifp.close()
752 752 ofp.close()
753 753 except:
754 754 try: os.unlink(temp)
755 755 except: pass
756 756 raise
757 757 return temp
758 758
759 759 class atomictempfile(object):
760 760 '''writeable file object that atomically updates a file
761 761
762 762 All writes will go to a temporary copy of the original file. Call
763 763 close() when you are done writing, and atomictempfile will rename
764 764 the temporary copy to the original name, making the changes
765 765 visible. If the object is destroyed without being closed, all your
766 766 writes are discarded.
767 767 '''
768 768 def __init__(self, name, mode='w+b', createmode=None):
769 769 self.__name = name # permanent name
770 770 self._tempname = mktempcopy(name, emptyok=('w' in mode),
771 771 createmode=createmode)
772 772 self._fp = posixfile(self._tempname, mode)
773 773
774 774 # delegated methods
775 775 self.write = self._fp.write
776 776 self.fileno = self._fp.fileno
777 777
778 778 def close(self):
779 779 if not self._fp.closed:
780 780 self._fp.close()
781 781 rename(self._tempname, localpath(self.__name))
782 782
783 783 def discard(self):
784 784 if not self._fp.closed:
785 785 try:
786 786 os.unlink(self._tempname)
787 787 except OSError:
788 788 pass
789 789 self._fp.close()
790 790
791 791 def __del__(self):
792 792 if safehasattr(self, '_fp'): # constructor actually did something
793 793 self.discard()
794 794
795 795 def makedirs(name, mode=None):
796 796 """recursive directory creation with parent mode inheritance"""
797 797 try:
798 798 os.mkdir(name)
799 799 except OSError, err:
800 800 if err.errno == errno.EEXIST:
801 801 return
802 802 if err.errno != errno.ENOENT or not name:
803 803 raise
804 804 parent = os.path.dirname(os.path.abspath(name))
805 805 if parent == name:
806 806 raise
807 807 makedirs(parent, mode)
808 808 os.mkdir(name)
809 809 if mode is not None:
810 810 os.chmod(name, mode)
811 811
812 812 def readfile(path):
813 813 fp = open(path, 'rb')
814 814 try:
815 815 return fp.read()
816 816 finally:
817 817 fp.close()
818 818
819 819 def writefile(path, text):
820 820 fp = open(path, 'wb')
821 821 try:
822 822 fp.write(text)
823 823 finally:
824 824 fp.close()
825 825
826 826 def appendfile(path, text):
827 827 fp = open(path, 'ab')
828 828 try:
829 829 fp.write(text)
830 830 finally:
831 831 fp.close()
832 832
833 833 class chunkbuffer(object):
834 834 """Allow arbitrary sized chunks of data to be efficiently read from an
835 835 iterator over chunks of arbitrary size."""
836 836
837 837 def __init__(self, in_iter):
838 838 """in_iter is the iterator that's iterating over the input chunks.
839 839 targetsize is how big a buffer to try to maintain."""
840 840 def splitbig(chunks):
841 841 for chunk in chunks:
842 842 if len(chunk) > 2**20:
843 843 pos = 0
844 844 while pos < len(chunk):
845 845 end = pos + 2 ** 18
846 846 yield chunk[pos:end]
847 847 pos = end
848 848 else:
849 849 yield chunk
850 850 self.iter = splitbig(in_iter)
851 851 self._queue = []
852 852
853 853 def read(self, l):
854 854 """Read L bytes of data from the iterator of chunks of data.
855 855 Returns less than L bytes if the iterator runs dry."""
856 856 left = l
857 857 buf = ''
858 858 queue = self._queue
859 859 while left > 0:
860 860 # refill the queue
861 861 if not queue:
862 862 target = 2**18
863 863 for chunk in self.iter:
864 864 queue.append(chunk)
865 865 target -= len(chunk)
866 866 if target <= 0:
867 867 break
868 868 if not queue:
869 869 break
870 870
871 871 chunk = queue.pop(0)
872 872 left -= len(chunk)
873 873 if left < 0:
874 874 queue.insert(0, chunk[left:])
875 875 buf += chunk[:left]
876 876 else:
877 877 buf += chunk
878 878
879 879 return buf
880 880
881 881 def filechunkiter(f, size=65536, limit=None):
882 882 """Create a generator that produces the data in the file size
883 883 (default 65536) bytes at a time, up to optional limit (default is
884 884 to read all data). Chunks may be less than size bytes if the
885 885 chunk is the last chunk in the file, or the file is a socket or
886 886 some other type of file that sometimes reads less data than is
887 887 requested."""
888 888 assert size >= 0
889 889 assert limit is None or limit >= 0
890 890 while True:
891 891 if limit is None:
892 892 nbytes = size
893 893 else:
894 894 nbytes = min(limit, size)
895 895 s = nbytes and f.read(nbytes)
896 896 if not s:
897 897 break
898 898 if limit:
899 899 limit -= len(s)
900 900 yield s
901 901
902 902 def makedate():
903 lt = time.localtime()
904 if lt[8] == 1 and time.daylight:
905 tz = time.altzone
906 else:
907 tz = time.timezone
908 t = time.mktime(lt)
909 if t < 0:
903 ct = time.time()
904 if ct < 0:
910 905 hint = _("check your clock")
911 raise Abort(_("negative timestamp: %d") % t, hint=hint)
912 return t, tz
906 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
907 delta = (datetime.datetime.utcfromtimestamp(ct) -
908 datetime.datetime.fromtimestamp(ct))
909 tz = delta.days * 86400 + delta.seconds
910 return ct, tz
913 911
914 912 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
915 913 """represent a (unixtime, offset) tuple as a localized time.
916 914 unixtime is seconds since the epoch, and offset is the time zone's
917 915 number of seconds away from UTC. if timezone is false, do not
918 916 append time zone to string."""
919 917 t, tz = date or makedate()
920 918 if t < 0:
921 919 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
922 920 tz = 0
923 921 if "%1" in format or "%2" in format:
924 922 sign = (tz > 0) and "-" or "+"
925 923 minutes = abs(tz) // 60
926 924 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
927 925 format = format.replace("%2", "%02d" % (minutes % 60))
928 926 try:
929 927 t = time.gmtime(float(t) - tz)
930 928 except ValueError:
931 929 # time was out of range
932 930 t = time.gmtime(sys.maxint)
933 931 s = time.strftime(format, t)
934 932 return s
935 933
936 934 def shortdate(date=None):
937 935 """turn (timestamp, tzoff) tuple into iso 8631 date."""
938 936 return datestr(date, format='%Y-%m-%d')
939 937
940 938 def strdate(string, format, defaults=[]):
941 939 """parse a localized time string and return a (unixtime, offset) tuple.
942 940 if the string cannot be parsed, ValueError is raised."""
943 941 def timezone(string):
944 942 tz = string.split()[-1]
945 943 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
946 944 sign = (tz[0] == "+") and 1 or -1
947 945 hours = int(tz[1:3])
948 946 minutes = int(tz[3:5])
949 947 return -sign * (hours * 60 + minutes) * 60
950 948 if tz == "GMT" or tz == "UTC":
951 949 return 0
952 950 return None
953 951
954 952 # NOTE: unixtime = localunixtime + offset
955 953 offset, date = timezone(string), string
956 954 if offset is not None:
957 955 date = " ".join(string.split()[:-1])
958 956
959 957 # add missing elements from defaults
960 958 usenow = False # default to using biased defaults
961 959 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
962 960 found = [True for p in part if ("%"+p) in format]
963 961 if not found:
964 962 date += "@" + defaults[part][usenow]
965 963 format += "@%" + part[0]
966 964 else:
967 965 # We've found a specific time element, less specific time
968 966 # elements are relative to today
969 967 usenow = True
970 968
971 969 timetuple = time.strptime(date, format)
972 970 localunixtime = int(calendar.timegm(timetuple))
973 971 if offset is None:
974 972 # local timezone
975 973 unixtime = int(time.mktime(timetuple))
976 974 offset = unixtime - localunixtime
977 975 else:
978 976 unixtime = localunixtime + offset
979 977 return unixtime, offset
980 978
981 979 def parsedate(date, formats=None, bias={}):
982 980 """parse a localized date/time and return a (unixtime, offset) tuple.
983 981
984 982 The date may be a "unixtime offset" string or in one of the specified
985 983 formats. If the date already is a (unixtime, offset) tuple, it is returned.
986 984 """
987 985 if not date:
988 986 return 0, 0
989 987 if isinstance(date, tuple) and len(date) == 2:
990 988 return date
991 989 if not formats:
992 990 formats = defaultdateformats
993 991 date = date.strip()
994 992 try:
995 993 when, offset = map(int, date.split(' '))
996 994 except ValueError:
997 995 # fill out defaults
998 996 now = makedate()
999 997 defaults = {}
1000 998 for part in ("d", "mb", "yY", "HI", "M", "S"):
1001 999 # this piece is for rounding the specific end of unknowns
1002 1000 b = bias.get(part)
1003 1001 if b is None:
1004 1002 if part[0] in "HMS":
1005 1003 b = "00"
1006 1004 else:
1007 1005 b = "0"
1008 1006
1009 1007 # this piece is for matching the generic end to today's date
1010 1008 n = datestr(now, "%" + part[0])
1011 1009
1012 1010 defaults[part] = (b, n)
1013 1011
1014 1012 for format in formats:
1015 1013 try:
1016 1014 when, offset = strdate(date, format, defaults)
1017 1015 except (ValueError, OverflowError):
1018 1016 pass
1019 1017 else:
1020 1018 break
1021 1019 else:
1022 1020 raise Abort(_('invalid date: %r') % date)
1023 1021 # validate explicit (probably user-specified) date and
1024 1022 # time zone offset. values must fit in signed 32 bits for
1025 1023 # current 32-bit linux runtimes. timezones go from UTC-12
1026 1024 # to UTC+14
1027 1025 if abs(when) > 0x7fffffff:
1028 1026 raise Abort(_('date exceeds 32 bits: %d') % when)
1029 1027 if when < 0:
1030 1028 raise Abort(_('negative date value: %d') % when)
1031 1029 if offset < -50400 or offset > 43200:
1032 1030 raise Abort(_('impossible time zone offset: %d') % offset)
1033 1031 return when, offset
1034 1032
1035 1033 def matchdate(date):
1036 1034 """Return a function that matches a given date match specifier
1037 1035
1038 1036 Formats include:
1039 1037
1040 1038 '{date}' match a given date to the accuracy provided
1041 1039
1042 1040 '<{date}' on or before a given date
1043 1041
1044 1042 '>{date}' on or after a given date
1045 1043
1046 1044 >>> p1 = parsedate("10:29:59")
1047 1045 >>> p2 = parsedate("10:30:00")
1048 1046 >>> p3 = parsedate("10:30:59")
1049 1047 >>> p4 = parsedate("10:31:00")
1050 1048 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1051 1049 >>> f = matchdate("10:30")
1052 1050 >>> f(p1[0])
1053 1051 False
1054 1052 >>> f(p2[0])
1055 1053 True
1056 1054 >>> f(p3[0])
1057 1055 True
1058 1056 >>> f(p4[0])
1059 1057 False
1060 1058 >>> f(p5[0])
1061 1059 False
1062 1060 """
1063 1061
1064 1062 def lower(date):
1065 1063 d = dict(mb="1", d="1")
1066 1064 return parsedate(date, extendeddateformats, d)[0]
1067 1065
1068 1066 def upper(date):
1069 1067 d = dict(mb="12", HI="23", M="59", S="59")
1070 1068 for days in ("31", "30", "29"):
1071 1069 try:
1072 1070 d["d"] = days
1073 1071 return parsedate(date, extendeddateformats, d)[0]
1074 1072 except:
1075 1073 pass
1076 1074 d["d"] = "28"
1077 1075 return parsedate(date, extendeddateformats, d)[0]
1078 1076
1079 1077 date = date.strip()
1080 1078
1081 1079 if not date:
1082 1080 raise Abort(_("dates cannot consist entirely of whitespace"))
1083 1081 elif date[0] == "<":
1084 1082 if not date[1:]:
1085 1083 raise Abort(_("invalid day spec, use '<DATE'"))
1086 1084 when = upper(date[1:])
1087 1085 return lambda x: x <= when
1088 1086 elif date[0] == ">":
1089 1087 if not date[1:]:
1090 1088 raise Abort(_("invalid day spec, use '>DATE'"))
1091 1089 when = lower(date[1:])
1092 1090 return lambda x: x >= when
1093 1091 elif date[0] == "-":
1094 1092 try:
1095 1093 days = int(date[1:])
1096 1094 except ValueError:
1097 1095 raise Abort(_("invalid day spec: %s") % date[1:])
1098 1096 if days < 0:
1099 1097 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1100 1098 % date[1:])
1101 1099 when = makedate()[0] - days * 3600 * 24
1102 1100 return lambda x: x >= when
1103 1101 elif " to " in date:
1104 1102 a, b = date.split(" to ")
1105 1103 start, stop = lower(a), upper(b)
1106 1104 return lambda x: x >= start and x <= stop
1107 1105 else:
1108 1106 start, stop = lower(date), upper(date)
1109 1107 return lambda x: x >= start and x <= stop
1110 1108
1111 1109 def shortuser(user):
1112 1110 """Return a short representation of a user name or email address."""
1113 1111 f = user.find('@')
1114 1112 if f >= 0:
1115 1113 user = user[:f]
1116 1114 f = user.find('<')
1117 1115 if f >= 0:
1118 1116 user = user[f + 1:]
1119 1117 f = user.find(' ')
1120 1118 if f >= 0:
1121 1119 user = user[:f]
1122 1120 f = user.find('.')
1123 1121 if f >= 0:
1124 1122 user = user[:f]
1125 1123 return user
1126 1124
1127 1125 def email(author):
1128 1126 '''get email of author.'''
1129 1127 r = author.find('>')
1130 1128 if r == -1:
1131 1129 r = None
1132 1130 return author[author.find('<') + 1:r]
1133 1131
1134 1132 def _ellipsis(text, maxlength):
1135 1133 if len(text) <= maxlength:
1136 1134 return text, False
1137 1135 else:
1138 1136 return "%s..." % (text[:maxlength - 3]), True
1139 1137
1140 1138 def ellipsis(text, maxlength=400):
1141 1139 """Trim string to at most maxlength (default: 400) characters."""
1142 1140 try:
1143 1141 # use unicode not to split at intermediate multi-byte sequence
1144 1142 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1145 1143 maxlength)
1146 1144 if not truncated:
1147 1145 return text
1148 1146 return utext.encode(encoding.encoding)
1149 1147 except (UnicodeDecodeError, UnicodeEncodeError):
1150 1148 return _ellipsis(text, maxlength)[0]
1151 1149
1152 1150 def bytecount(nbytes):
1153 1151 '''return byte count formatted as readable string, with units'''
1154 1152
1155 1153 units = (
1156 1154 (100, 1 << 30, _('%.0f GB')),
1157 1155 (10, 1 << 30, _('%.1f GB')),
1158 1156 (1, 1 << 30, _('%.2f GB')),
1159 1157 (100, 1 << 20, _('%.0f MB')),
1160 1158 (10, 1 << 20, _('%.1f MB')),
1161 1159 (1, 1 << 20, _('%.2f MB')),
1162 1160 (100, 1 << 10, _('%.0f KB')),
1163 1161 (10, 1 << 10, _('%.1f KB')),
1164 1162 (1, 1 << 10, _('%.2f KB')),
1165 1163 (1, 1, _('%.0f bytes')),
1166 1164 )
1167 1165
1168 1166 for multiplier, divisor, format in units:
1169 1167 if nbytes >= divisor * multiplier:
1170 1168 return format % (nbytes / float(divisor))
1171 1169 return units[-1][2] % nbytes
1172 1170
1173 1171 def uirepr(s):
1174 1172 # Avoid double backslash in Windows path repr()
1175 1173 return repr(s).replace('\\\\', '\\')
1176 1174
1177 1175 # delay import of textwrap
1178 1176 def MBTextWrapper(**kwargs):
1179 1177 class tw(textwrap.TextWrapper):
1180 1178 """
1181 1179 Extend TextWrapper for width-awareness.
1182 1180
1183 1181 Neither number of 'bytes' in any encoding nor 'characters' is
1184 1182 appropriate to calculate terminal columns for specified string.
1185 1183
1186 1184 Original TextWrapper implementation uses built-in 'len()' directly,
1187 1185 so overriding is needed to use width information of each characters.
1188 1186
1189 1187 In addition, characters classified into 'ambiguous' width are
1190 1188 treated as wide in east asian area, but as narrow in other.
1191 1189
1192 1190 This requires use decision to determine width of such characters.
1193 1191 """
1194 1192 def __init__(self, **kwargs):
1195 1193 textwrap.TextWrapper.__init__(self, **kwargs)
1196 1194
1197 1195 # for compatibility between 2.4 and 2.6
1198 1196 if getattr(self, 'drop_whitespace', None) is None:
1199 1197 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1200 1198
1201 1199 def _cutdown(self, ucstr, space_left):
1202 1200 l = 0
1203 1201 colwidth = encoding.ucolwidth
1204 1202 for i in xrange(len(ucstr)):
1205 1203 l += colwidth(ucstr[i])
1206 1204 if space_left < l:
1207 1205 return (ucstr[:i], ucstr[i:])
1208 1206 return ucstr, ''
1209 1207
1210 1208 # overriding of base class
1211 1209 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1212 1210 space_left = max(width - cur_len, 1)
1213 1211
1214 1212 if self.break_long_words:
1215 1213 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1216 1214 cur_line.append(cut)
1217 1215 reversed_chunks[-1] = res
1218 1216 elif not cur_line:
1219 1217 cur_line.append(reversed_chunks.pop())
1220 1218
1221 1219 # this overriding code is imported from TextWrapper of python 2.6
1222 1220 # to calculate columns of string by 'encoding.ucolwidth()'
1223 1221 def _wrap_chunks(self, chunks):
1224 1222 colwidth = encoding.ucolwidth
1225 1223
1226 1224 lines = []
1227 1225 if self.width <= 0:
1228 1226 raise ValueError("invalid width %r (must be > 0)" % self.width)
1229 1227
1230 1228 # Arrange in reverse order so items can be efficiently popped
1231 1229 # from a stack of chucks.
1232 1230 chunks.reverse()
1233 1231
1234 1232 while chunks:
1235 1233
1236 1234 # Start the list of chunks that will make up the current line.
1237 1235 # cur_len is just the length of all the chunks in cur_line.
1238 1236 cur_line = []
1239 1237 cur_len = 0
1240 1238
1241 1239 # Figure out which static string will prefix this line.
1242 1240 if lines:
1243 1241 indent = self.subsequent_indent
1244 1242 else:
1245 1243 indent = self.initial_indent
1246 1244
1247 1245 # Maximum width for this line.
1248 1246 width = self.width - len(indent)
1249 1247
1250 1248 # First chunk on line is whitespace -- drop it, unless this
1251 1249 # is the very beginning of the text (ie. no lines started yet).
1252 1250 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1253 1251 del chunks[-1]
1254 1252
1255 1253 while chunks:
1256 1254 l = colwidth(chunks[-1])
1257 1255
1258 1256 # Can at least squeeze this chunk onto the current line.
1259 1257 if cur_len + l <= width:
1260 1258 cur_line.append(chunks.pop())
1261 1259 cur_len += l
1262 1260
1263 1261 # Nope, this line is full.
1264 1262 else:
1265 1263 break
1266 1264
1267 1265 # The current line is full, and the next chunk is too big to
1268 1266 # fit on *any* line (not just this one).
1269 1267 if chunks and colwidth(chunks[-1]) > width:
1270 1268 self._handle_long_word(chunks, cur_line, cur_len, width)
1271 1269
1272 1270 # If the last chunk on this line is all whitespace, drop it.
1273 1271 if (self.drop_whitespace and
1274 1272 cur_line and cur_line[-1].strip() == ''):
1275 1273 del cur_line[-1]
1276 1274
1277 1275 # Convert current line back to a string and store it in list
1278 1276 # of all lines (return value).
1279 1277 if cur_line:
1280 1278 lines.append(indent + ''.join(cur_line))
1281 1279
1282 1280 return lines
1283 1281
1284 1282 global MBTextWrapper
1285 1283 MBTextWrapper = tw
1286 1284 return tw(**kwargs)
1287 1285
1288 1286 def wrap(line, width, initindent='', hangindent=''):
1289 1287 maxindent = max(len(hangindent), len(initindent))
1290 1288 if width <= maxindent:
1291 1289 # adjust for weird terminal size
1292 1290 width = max(78, maxindent + 1)
1293 1291 line = line.decode(encoding.encoding, encoding.encodingmode)
1294 1292 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1295 1293 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1296 1294 wrapper = MBTextWrapper(width=width,
1297 1295 initial_indent=initindent,
1298 1296 subsequent_indent=hangindent)
1299 1297 return wrapper.fill(line).encode(encoding.encoding)
1300 1298
1301 1299 def iterlines(iterator):
1302 1300 for chunk in iterator:
1303 1301 for line in chunk.splitlines():
1304 1302 yield line
1305 1303
1306 1304 def expandpath(path):
1307 1305 return os.path.expanduser(os.path.expandvars(path))
1308 1306
1309 1307 def hgcmd():
1310 1308 """Return the command used to execute current hg
1311 1309
1312 1310 This is different from hgexecutable() because on Windows we want
1313 1311 to avoid things opening new shell windows like batch files, so we
1314 1312 get either the python call or current executable.
1315 1313 """
1316 1314 if mainfrozen():
1317 1315 return [sys.executable]
1318 1316 return gethgcmd()
1319 1317
1320 1318 def rundetached(args, condfn):
1321 1319 """Execute the argument list in a detached process.
1322 1320
1323 1321 condfn is a callable which is called repeatedly and should return
1324 1322 True once the child process is known to have started successfully.
1325 1323 At this point, the child process PID is returned. If the child
1326 1324 process fails to start or finishes before condfn() evaluates to
1327 1325 True, return -1.
1328 1326 """
1329 1327 # Windows case is easier because the child process is either
1330 1328 # successfully starting and validating the condition or exiting
1331 1329 # on failure. We just poll on its PID. On Unix, if the child
1332 1330 # process fails to start, it will be left in a zombie state until
1333 1331 # the parent wait on it, which we cannot do since we expect a long
1334 1332 # running process on success. Instead we listen for SIGCHLD telling
1335 1333 # us our child process terminated.
1336 1334 terminated = set()
1337 1335 def handler(signum, frame):
1338 1336 terminated.add(os.wait())
1339 1337 prevhandler = None
1340 1338 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1341 1339 if SIGCHLD is not None:
1342 1340 prevhandler = signal.signal(SIGCHLD, handler)
1343 1341 try:
1344 1342 pid = spawndetached(args)
1345 1343 while not condfn():
1346 1344 if ((pid in terminated or not testpid(pid))
1347 1345 and not condfn()):
1348 1346 return -1
1349 1347 time.sleep(0.1)
1350 1348 return pid
1351 1349 finally:
1352 1350 if prevhandler is not None:
1353 1351 signal.signal(signal.SIGCHLD, prevhandler)
1354 1352
1355 1353 try:
1356 1354 any, all = any, all
1357 1355 except NameError:
1358 1356 def any(iterable):
1359 1357 for i in iterable:
1360 1358 if i:
1361 1359 return True
1362 1360 return False
1363 1361
1364 1362 def all(iterable):
1365 1363 for i in iterable:
1366 1364 if not i:
1367 1365 return False
1368 1366 return True
1369 1367
1370 1368 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1371 1369 """Return the result of interpolating items in the mapping into string s.
1372 1370
1373 1371 prefix is a single character string, or a two character string with
1374 1372 a backslash as the first character if the prefix needs to be escaped in
1375 1373 a regular expression.
1376 1374
1377 1375 fn is an optional function that will be applied to the replacement text
1378 1376 just before replacement.
1379 1377
1380 1378 escape_prefix is an optional flag that allows using doubled prefix for
1381 1379 its escaping.
1382 1380 """
1383 1381 fn = fn or (lambda s: s)
1384 1382 patterns = '|'.join(mapping.keys())
1385 1383 if escape_prefix:
1386 1384 patterns += '|' + prefix
1387 1385 if len(prefix) > 1:
1388 1386 prefix_char = prefix[1:]
1389 1387 else:
1390 1388 prefix_char = prefix
1391 1389 mapping[prefix_char] = prefix_char
1392 1390 r = re.compile(r'%s(%s)' % (prefix, patterns))
1393 1391 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1394 1392
1395 1393 def getport(port):
1396 1394 """Return the port for a given network service.
1397 1395
1398 1396 If port is an integer, it's returned as is. If it's a string, it's
1399 1397 looked up using socket.getservbyname(). If there's no matching
1400 1398 service, util.Abort is raised.
1401 1399 """
1402 1400 try:
1403 1401 return int(port)
1404 1402 except ValueError:
1405 1403 pass
1406 1404
1407 1405 try:
1408 1406 return socket.getservbyname(port)
1409 1407 except socket.error:
1410 1408 raise Abort(_("no port number associated with service '%s'") % port)
1411 1409
1412 1410 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1413 1411 '0': False, 'no': False, 'false': False, 'off': False,
1414 1412 'never': False}
1415 1413
1416 1414 def parsebool(s):
1417 1415 """Parse s into a boolean.
1418 1416
1419 1417 If s is not a valid boolean, returns None.
1420 1418 """
1421 1419 return _booleans.get(s.lower(), None)
1422 1420
1423 1421 _hexdig = '0123456789ABCDEFabcdef'
1424 1422 _hextochr = dict((a + b, chr(int(a + b, 16)))
1425 1423 for a in _hexdig for b in _hexdig)
1426 1424
1427 1425 def _urlunquote(s):
1428 1426 """unquote('abc%20def') -> 'abc def'."""
1429 1427 res = s.split('%')
1430 1428 # fastpath
1431 1429 if len(res) == 1:
1432 1430 return s
1433 1431 s = res[0]
1434 1432 for item in res[1:]:
1435 1433 try:
1436 1434 s += _hextochr[item[:2]] + item[2:]
1437 1435 except KeyError:
1438 1436 s += '%' + item
1439 1437 except UnicodeDecodeError:
1440 1438 s += unichr(int(item[:2], 16)) + item[2:]
1441 1439 return s
1442 1440
1443 1441 class url(object):
1444 1442 r"""Reliable URL parser.
1445 1443
1446 1444 This parses URLs and provides attributes for the following
1447 1445 components:
1448 1446
1449 1447 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1450 1448
1451 1449 Missing components are set to None. The only exception is
1452 1450 fragment, which is set to '' if present but empty.
1453 1451
1454 1452 If parsefragment is False, fragment is included in query. If
1455 1453 parsequery is False, query is included in path. If both are
1456 1454 False, both fragment and query are included in path.
1457 1455
1458 1456 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1459 1457
1460 1458 Note that for backward compatibility reasons, bundle URLs do not
1461 1459 take host names. That means 'bundle://../' has a path of '../'.
1462 1460
1463 1461 Examples:
1464 1462
1465 1463 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1466 1464 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1467 1465 >>> url('ssh://[::1]:2200//home/joe/repo')
1468 1466 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1469 1467 >>> url('file:///home/joe/repo')
1470 1468 <url scheme: 'file', path: '/home/joe/repo'>
1471 1469 >>> url('file:///c:/temp/foo/')
1472 1470 <url scheme: 'file', path: 'c:/temp/foo/'>
1473 1471 >>> url('bundle:foo')
1474 1472 <url scheme: 'bundle', path: 'foo'>
1475 1473 >>> url('bundle://../foo')
1476 1474 <url scheme: 'bundle', path: '../foo'>
1477 1475 >>> url(r'c:\foo\bar')
1478 1476 <url path: 'c:\\foo\\bar'>
1479 1477 >>> url(r'\\blah\blah\blah')
1480 1478 <url path: '\\\\blah\\blah\\blah'>
1481 1479 >>> url(r'\\blah\blah\blah#baz')
1482 1480 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1483 1481
1484 1482 Authentication credentials:
1485 1483
1486 1484 >>> url('ssh://joe:xyz@x/repo')
1487 1485 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1488 1486 >>> url('ssh://joe@x/repo')
1489 1487 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1490 1488
1491 1489 Query strings and fragments:
1492 1490
1493 1491 >>> url('http://host/a?b#c')
1494 1492 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1495 1493 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1496 1494 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1497 1495 """
1498 1496
1499 1497 _safechars = "!~*'()+"
1500 1498 _safepchars = "/!~*'()+:"
1501 1499 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1502 1500
1503 1501 def __init__(self, path, parsequery=True, parsefragment=True):
1504 1502 # We slowly chomp away at path until we have only the path left
1505 1503 self.scheme = self.user = self.passwd = self.host = None
1506 1504 self.port = self.path = self.query = self.fragment = None
1507 1505 self._localpath = True
1508 1506 self._hostport = ''
1509 1507 self._origpath = path
1510 1508
1511 1509 if parsefragment and '#' in path:
1512 1510 path, self.fragment = path.split('#', 1)
1513 1511 if not path:
1514 1512 path = None
1515 1513
1516 1514 # special case for Windows drive letters and UNC paths
1517 1515 if hasdriveletter(path) or path.startswith(r'\\'):
1518 1516 self.path = path
1519 1517 return
1520 1518
1521 1519 # For compatibility reasons, we can't handle bundle paths as
1522 1520 # normal URLS
1523 1521 if path.startswith('bundle:'):
1524 1522 self.scheme = 'bundle'
1525 1523 path = path[7:]
1526 1524 if path.startswith('//'):
1527 1525 path = path[2:]
1528 1526 self.path = path
1529 1527 return
1530 1528
1531 1529 if self._matchscheme(path):
1532 1530 parts = path.split(':', 1)
1533 1531 if parts[0]:
1534 1532 self.scheme, path = parts
1535 1533 self._localpath = False
1536 1534
1537 1535 if not path:
1538 1536 path = None
1539 1537 if self._localpath:
1540 1538 self.path = ''
1541 1539 return
1542 1540 else:
1543 1541 if self._localpath:
1544 1542 self.path = path
1545 1543 return
1546 1544
1547 1545 if parsequery and '?' in path:
1548 1546 path, self.query = path.split('?', 1)
1549 1547 if not path:
1550 1548 path = None
1551 1549 if not self.query:
1552 1550 self.query = None
1553 1551
1554 1552 # // is required to specify a host/authority
1555 1553 if path and path.startswith('//'):
1556 1554 parts = path[2:].split('/', 1)
1557 1555 if len(parts) > 1:
1558 1556 self.host, path = parts
1559 1557 path = path
1560 1558 else:
1561 1559 self.host = parts[0]
1562 1560 path = None
1563 1561 if not self.host:
1564 1562 self.host = None
1565 1563 # path of file:///d is /d
1566 1564 # path of file:///d:/ is d:/, not /d:/
1567 1565 if path and not hasdriveletter(path):
1568 1566 path = '/' + path
1569 1567
1570 1568 if self.host and '@' in self.host:
1571 1569 self.user, self.host = self.host.rsplit('@', 1)
1572 1570 if ':' in self.user:
1573 1571 self.user, self.passwd = self.user.split(':', 1)
1574 1572 if not self.host:
1575 1573 self.host = None
1576 1574
1577 1575 # Don't split on colons in IPv6 addresses without ports
1578 1576 if (self.host and ':' in self.host and
1579 1577 not (self.host.startswith('[') and self.host.endswith(']'))):
1580 1578 self._hostport = self.host
1581 1579 self.host, self.port = self.host.rsplit(':', 1)
1582 1580 if not self.host:
1583 1581 self.host = None
1584 1582
1585 1583 if (self.host and self.scheme == 'file' and
1586 1584 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1587 1585 raise Abort(_('file:// URLs can only refer to localhost'))
1588 1586
1589 1587 self.path = path
1590 1588
1591 1589 # leave the query string escaped
1592 1590 for a in ('user', 'passwd', 'host', 'port',
1593 1591 'path', 'fragment'):
1594 1592 v = getattr(self, a)
1595 1593 if v is not None:
1596 1594 setattr(self, a, _urlunquote(v))
1597 1595
1598 1596 def __repr__(self):
1599 1597 attrs = []
1600 1598 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1601 1599 'query', 'fragment'):
1602 1600 v = getattr(self, a)
1603 1601 if v is not None:
1604 1602 attrs.append('%s: %r' % (a, v))
1605 1603 return '<url %s>' % ', '.join(attrs)
1606 1604
1607 1605 def __str__(self):
1608 1606 r"""Join the URL's components back into a URL string.
1609 1607
1610 1608 Examples:
1611 1609
1612 1610 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1613 1611 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1614 1612 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1615 1613 'http://user:pw@host:80/?foo=bar&baz=42'
1616 1614 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1617 1615 'http://user:pw@host:80/?foo=bar%3dbaz'
1618 1616 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1619 1617 'ssh://user:pw@[::1]:2200//home/joe#'
1620 1618 >>> str(url('http://localhost:80//'))
1621 1619 'http://localhost:80//'
1622 1620 >>> str(url('http://localhost:80/'))
1623 1621 'http://localhost:80/'
1624 1622 >>> str(url('http://localhost:80'))
1625 1623 'http://localhost:80/'
1626 1624 >>> str(url('bundle:foo'))
1627 1625 'bundle:foo'
1628 1626 >>> str(url('bundle://../foo'))
1629 1627 'bundle:../foo'
1630 1628 >>> str(url('path'))
1631 1629 'path'
1632 1630 >>> str(url('file:///tmp/foo/bar'))
1633 1631 'file:///tmp/foo/bar'
1634 1632 >>> print url(r'bundle:foo\bar')
1635 1633 bundle:foo\bar
1636 1634 """
1637 1635 if self._localpath:
1638 1636 s = self.path
1639 1637 if self.scheme == 'bundle':
1640 1638 s = 'bundle:' + s
1641 1639 if self.fragment:
1642 1640 s += '#' + self.fragment
1643 1641 return s
1644 1642
1645 1643 s = self.scheme + ':'
1646 1644 if self.user or self.passwd or self.host:
1647 1645 s += '//'
1648 1646 elif self.scheme and (not self.path or self.path.startswith('/')):
1649 1647 s += '//'
1650 1648 if self.user:
1651 1649 s += urllib.quote(self.user, safe=self._safechars)
1652 1650 if self.passwd:
1653 1651 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1654 1652 if self.user or self.passwd:
1655 1653 s += '@'
1656 1654 if self.host:
1657 1655 if not (self.host.startswith('[') and self.host.endswith(']')):
1658 1656 s += urllib.quote(self.host)
1659 1657 else:
1660 1658 s += self.host
1661 1659 if self.port:
1662 1660 s += ':' + urllib.quote(self.port)
1663 1661 if self.host:
1664 1662 s += '/'
1665 1663 if self.path:
1666 1664 # TODO: similar to the query string, we should not unescape the
1667 1665 # path when we store it, the path might contain '%2f' = '/',
1668 1666 # which we should *not* escape.
1669 1667 s += urllib.quote(self.path, safe=self._safepchars)
1670 1668 if self.query:
1671 1669 # we store the query in escaped form.
1672 1670 s += '?' + self.query
1673 1671 if self.fragment is not None:
1674 1672 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1675 1673 return s
1676 1674
1677 1675 def authinfo(self):
1678 1676 user, passwd = self.user, self.passwd
1679 1677 try:
1680 1678 self.user, self.passwd = None, None
1681 1679 s = str(self)
1682 1680 finally:
1683 1681 self.user, self.passwd = user, passwd
1684 1682 if not self.user:
1685 1683 return (s, None)
1686 1684 # authinfo[1] is passed to urllib2 password manager, and its
1687 1685 # URIs must not contain credentials. The host is passed in the
1688 1686 # URIs list because Python < 2.4.3 uses only that to search for
1689 1687 # a password.
1690 1688 return (s, (None, (s, self.host),
1691 1689 self.user, self.passwd or ''))
1692 1690
1693 1691 def isabs(self):
1694 1692 if self.scheme and self.scheme != 'file':
1695 1693 return True # remote URL
1696 1694 if hasdriveletter(self.path):
1697 1695 return True # absolute for our purposes - can't be joined()
1698 1696 if self.path.startswith(r'\\'):
1699 1697 return True # Windows UNC path
1700 1698 if self.path.startswith('/'):
1701 1699 return True # POSIX-style
1702 1700 return False
1703 1701
1704 1702 def localpath(self):
1705 1703 if self.scheme == 'file' or self.scheme == 'bundle':
1706 1704 path = self.path or '/'
1707 1705 # For Windows, we need to promote hosts containing drive
1708 1706 # letters to paths with drive letters.
1709 1707 if hasdriveletter(self._hostport):
1710 1708 path = self._hostport + '/' + self.path
1711 elif self.host is not None and self.path:
1709 elif (self.host is not None and self.path
1710 and not hasdriveletter(path)):
1712 1711 path = '/' + path
1713 1712 return path
1714 1713 return self._origpath
1715 1714
1716 1715 def hasscheme(path):
1717 1716 return bool(url(path).scheme)
1718 1717
1719 1718 def hasdriveletter(path):
1720 1719 return path[1:2] == ':' and path[0:1].isalpha()
1721 1720
1722 1721 def urllocalpath(path):
1723 1722 return url(path, parsequery=False, parsefragment=False).localpath()
1724 1723
1725 1724 def hidepassword(u):
1726 1725 '''hide user credential in a url string'''
1727 1726 u = url(u)
1728 1727 if u.passwd:
1729 1728 u.passwd = '***'
1730 1729 return str(u)
1731 1730
1732 1731 def removeauth(u):
1733 1732 '''remove all authentication information from a url string'''
1734 1733 u = url(u)
1735 1734 u.user = u.passwd = None
1736 1735 return str(u)
1737 1736
1738 1737 def isatty(fd):
1739 1738 try:
1740 1739 return fd.isatty()
1741 1740 except AttributeError:
1742 1741 return False
@@ -1,444 +1,457 b''
1 1 GNU diff is the reference for all of these results.
2 2
3 3 Prepare tests:
4 4
5 5 $ echo '[alias]' >> $HGRCPATH
6 6 $ echo 'ndiff = diff --nodates' >> $HGRCPATH
7 7
8 8 $ hg init
9 9 $ printf 'hello world\ngoodbye world\n' >foo
10 10 $ hg ci -Amfoo -ufoo
11 11 adding foo
12 12
13 13
14 14 Test added blank lines:
15 15
16 16 $ printf '\nhello world\n\ngoodbye world\n\n' >foo
17 17
18 18 >>> two diffs showing three added lines <<<
19 19
20 20 $ hg ndiff
21 21 diff -r 540c40a65b78 foo
22 22 --- a/foo
23 23 +++ b/foo
24 24 @@ -1,2 +1,5 @@
25 25 +
26 26 hello world
27 27 +
28 28 goodbye world
29 29 +
30 30 $ hg ndiff -b
31 31 diff -r 540c40a65b78 foo
32 32 --- a/foo
33 33 +++ b/foo
34 34 @@ -1,2 +1,5 @@
35 35 +
36 36 hello world
37 37 +
38 38 goodbye world
39 39 +
40 40
41 41 >>> no diffs <<<
42 42
43 43 $ hg ndiff -B
44 44 $ hg ndiff -Bb
45 45
46 46
47 47 Test added horizontal space first on a line():
48 48
49 49 $ printf '\t hello world\ngoodbye world\n' >foo
50 50
51 51 >>> four diffs showing added space first on the first line <<<
52 52
53 53 $ hg ndiff
54 54 diff -r 540c40a65b78 foo
55 55 --- a/foo
56 56 +++ b/foo
57 57 @@ -1,2 +1,2 @@
58 58 -hello world
59 59 + hello world
60 60 goodbye world
61 61
62 62 $ hg ndiff -b
63 63 diff -r 540c40a65b78 foo
64 64 --- a/foo
65 65 +++ b/foo
66 66 @@ -1,2 +1,2 @@
67 67 -hello world
68 68 + hello world
69 69 goodbye world
70 70
71 71 $ hg ndiff -B
72 72 diff -r 540c40a65b78 foo
73 73 --- a/foo
74 74 +++ b/foo
75 75 @@ -1,2 +1,2 @@
76 76 -hello world
77 77 + hello world
78 78 goodbye world
79 79
80 80 $ hg ndiff -Bb
81 81 diff -r 540c40a65b78 foo
82 82 --- a/foo
83 83 +++ b/foo
84 84 @@ -1,2 +1,2 @@
85 85 -hello world
86 86 + hello world
87 87 goodbye world
88 88
89 89
90 90 Test added horizontal space last on a line:
91 91
92 92 $ printf 'hello world\t \ngoodbye world\n' >foo
93 93
94 94 >>> two diffs showing space appended to the first line <<<
95 95
96 96 $ hg ndiff
97 97 diff -r 540c40a65b78 foo
98 98 --- a/foo
99 99 +++ b/foo
100 100 @@ -1,2 +1,2 @@
101 101 -hello world
102 102 +hello world
103 103 goodbye world
104 104
105 105 $ hg ndiff -B
106 106 diff -r 540c40a65b78 foo
107 107 --- a/foo
108 108 +++ b/foo
109 109 @@ -1,2 +1,2 @@
110 110 -hello world
111 111 +hello world
112 112 goodbye world
113 113
114 114 >>> no diffs <<<
115 115
116 116 $ hg ndiff -b
117 117 $ hg ndiff -Bb
118 118
119 119
120 120 Test added horizontal space in the middle of a word:
121 121
122 122 $ printf 'hello world\ngood bye world\n' >foo
123 123
124 124 >>> four diffs showing space inserted into "goodbye" <<<
125 125
126 126 $ hg ndiff
127 127 diff -r 540c40a65b78 foo
128 128 --- a/foo
129 129 +++ b/foo
130 130 @@ -1,2 +1,2 @@
131 131 hello world
132 132 -goodbye world
133 133 +good bye world
134 134
135 135 $ hg ndiff -B
136 136 diff -r 540c40a65b78 foo
137 137 --- a/foo
138 138 +++ b/foo
139 139 @@ -1,2 +1,2 @@
140 140 hello world
141 141 -goodbye world
142 142 +good bye world
143 143
144 144 $ hg ndiff -b
145 145 diff -r 540c40a65b78 foo
146 146 --- a/foo
147 147 +++ b/foo
148 148 @@ -1,2 +1,2 @@
149 149 hello world
150 150 -goodbye world
151 151 +good bye world
152 152
153 153 $ hg ndiff -Bb
154 154 diff -r 540c40a65b78 foo
155 155 --- a/foo
156 156 +++ b/foo
157 157 @@ -1,2 +1,2 @@
158 158 hello world
159 159 -goodbye world
160 160 +good bye world
161 161
162 162
163 163 Test increased horizontal whitespace amount:
164 164
165 165 $ printf 'hello world\ngoodbye\t\t \tworld\n' >foo
166 166
167 167 >>> two diffs showing changed whitespace amount in the last line <<<
168 168
169 169 $ hg ndiff
170 170 diff -r 540c40a65b78 foo
171 171 --- a/foo
172 172 +++ b/foo
173 173 @@ -1,2 +1,2 @@
174 174 hello world
175 175 -goodbye world
176 176 +goodbye world
177 177
178 178 $ hg ndiff -B
179 179 diff -r 540c40a65b78 foo
180 180 --- a/foo
181 181 +++ b/foo
182 182 @@ -1,2 +1,2 @@
183 183 hello world
184 184 -goodbye world
185 185 +goodbye world
186 186
187 187 >>> no diffs <<<
188 188
189 189 $ hg ndiff -b
190 190 $ hg ndiff -Bb
191 191
192 192
193 193 Test added blank line with horizontal whitespace:
194 194
195 195 $ printf 'hello world\n \t\ngoodbye world\n' >foo
196 196
197 197 >>> three diffs showing added blank line with horizontal space <<<
198 198
199 199 $ hg ndiff
200 200 diff -r 540c40a65b78 foo
201 201 --- a/foo
202 202 +++ b/foo
203 203 @@ -1,2 +1,3 @@
204 204 hello world
205 205 +
206 206 goodbye world
207 207
208 208 $ hg ndiff -B
209 209 diff -r 540c40a65b78 foo
210 210 --- a/foo
211 211 +++ b/foo
212 212 @@ -1,2 +1,3 @@
213 213 hello world
214 214 +
215 215 goodbye world
216 216
217 217 $ hg ndiff -b
218 218 diff -r 540c40a65b78 foo
219 219 --- a/foo
220 220 +++ b/foo
221 221 @@ -1,2 +1,3 @@
222 222 hello world
223 223 +
224 224 goodbye world
225 225
226 226 >>> no diffs <<<
227 227
228 228 $ hg ndiff -Bb
229 229
230 230
231 231 Test added blank line with other whitespace:
232 232
233 233 $ printf 'hello world\n \t\ngoodbye world \n' >foo
234 234
235 235 >>> three diffs showing added blank line with other space <<<
236 236
237 237 $ hg ndiff
238 238 diff -r 540c40a65b78 foo
239 239 --- a/foo
240 240 +++ b/foo
241 241 @@ -1,2 +1,3 @@
242 242 -hello world
243 243 -goodbye world
244 244 +hello world
245 245 +
246 246 +goodbye world
247 247
248 248 $ hg ndiff -B
249 249 diff -r 540c40a65b78 foo
250 250 --- a/foo
251 251 +++ b/foo
252 252 @@ -1,2 +1,3 @@
253 253 -hello world
254 254 -goodbye world
255 255 +hello world
256 256 +
257 257 +goodbye world
258 258
259 259 $ hg ndiff -b
260 260 diff -r 540c40a65b78 foo
261 261 --- a/foo
262 262 +++ b/foo
263 263 @@ -1,2 +1,3 @@
264 264 hello world
265 265 +
266 266 goodbye world
267 267
268 268 >>> no diffs <<<
269 269
270 270 $ hg ndiff -Bb
271 271
272 272
273 273 Test whitespace changes:
274 274
275 275 $ printf 'helloworld\ngoodbye\tworld \n' >foo
276 276
277 277 >>> four diffs showing changed whitespace <<<
278 278
279 279 $ hg ndiff
280 280 diff -r 540c40a65b78 foo
281 281 --- a/foo
282 282 +++ b/foo
283 283 @@ -1,2 +1,2 @@
284 284 -hello world
285 285 -goodbye world
286 286 +helloworld
287 287 +goodbye world
288 288
289 289 $ hg ndiff -B
290 290 diff -r 540c40a65b78 foo
291 291 --- a/foo
292 292 +++ b/foo
293 293 @@ -1,2 +1,2 @@
294 294 -hello world
295 295 -goodbye world
296 296 +helloworld
297 297 +goodbye world
298 298
299 299 $ hg ndiff -b
300 300 diff -r 540c40a65b78 foo
301 301 --- a/foo
302 302 +++ b/foo
303 303 @@ -1,2 +1,2 @@
304 304 -hello world
305 305 +helloworld
306 306 goodbye world
307 307
308 308 $ hg ndiff -Bb
309 309 diff -r 540c40a65b78 foo
310 310 --- a/foo
311 311 +++ b/foo
312 312 @@ -1,2 +1,2 @@
313 313 -hello world
314 314 +helloworld
315 315 goodbye world
316 316
317 317 >>> no diffs <<<
318 318
319 319 $ hg ndiff -w
320 320
321 321
322 322 Test whitespace changes and blank lines:
323 323
324 324 $ printf 'helloworld\n\n\n\ngoodbye\tworld \n' >foo
325 325
326 326 >>> five diffs showing changed whitespace <<<
327 327
328 328 $ hg ndiff
329 329 diff -r 540c40a65b78 foo
330 330 --- a/foo
331 331 +++ b/foo
332 332 @@ -1,2 +1,5 @@
333 333 -hello world
334 334 -goodbye world
335 335 +helloworld
336 336 +
337 337 +
338 338 +
339 339 +goodbye world
340 340
341 341 $ hg ndiff -B
342 342 diff -r 540c40a65b78 foo
343 343 --- a/foo
344 344 +++ b/foo
345 345 @@ -1,2 +1,5 @@
346 346 -hello world
347 347 -goodbye world
348 348 +helloworld
349 349 +
350 350 +
351 351 +
352 352 +goodbye world
353 353
354 354 $ hg ndiff -b
355 355 diff -r 540c40a65b78 foo
356 356 --- a/foo
357 357 +++ b/foo
358 358 @@ -1,2 +1,5 @@
359 359 -hello world
360 360 +helloworld
361 361 +
362 362 +
363 363 +
364 364 goodbye world
365 365
366 366 $ hg ndiff -Bb
367 367 diff -r 540c40a65b78 foo
368 368 --- a/foo
369 369 +++ b/foo
370 370 @@ -1,2 +1,5 @@
371 371 -hello world
372 372 +helloworld
373 373 +
374 374 +
375 375 +
376 376 goodbye world
377 377
378 378 $ hg ndiff -w
379 379 diff -r 540c40a65b78 foo
380 380 --- a/foo
381 381 +++ b/foo
382 382 @@ -1,2 +1,5 @@
383 383 hello world
384 384 +
385 385 +
386 386 +
387 387 goodbye world
388 388
389 389 >>> no diffs <<<
390 390
391 391 $ hg ndiff -wB
392 392
393 393
394 394 Test \r (carriage return) as used in "DOS" line endings:
395 395
396 396 $ printf 'hello world\r\n\r\ngoodbye\rworld\n' >foo
397 397
398 398 $ hg ndiff
399 399 diff -r 540c40a65b78 foo
400 400 --- a/foo
401 401 +++ b/foo
402 402 @@ -1,2 +1,3 @@
403 403 -hello world
404 404 -goodbye world
405 405 +hello world\r (esc)
406 406 +\r (esc)
407 407 +goodbye\rworld (esc)
408 408
409 409 No completely blank lines to ignore:
410 410
411 411 $ hg ndiff --ignore-blank-lines
412 412 diff -r 540c40a65b78 foo
413 413 --- a/foo
414 414 +++ b/foo
415 415 @@ -1,2 +1,3 @@
416 416 -hello world
417 417 -goodbye world
418 418 +hello world\r (esc)
419 419 +\r (esc)
420 420 +goodbye\rworld (esc)
421 421
422 422 Only new line noticed:
423 423
424 424 $ hg ndiff --ignore-space-change
425 425 diff -r 540c40a65b78 foo
426 426 --- a/foo
427 427 +++ b/foo
428 428 @@ -1,2 +1,3 @@
429 429 hello world
430 430 +\r (esc)
431 431 goodbye world
432 432
433 433 $ hg ndiff --ignore-all-space
434 434 diff -r 540c40a65b78 foo
435 435 --- a/foo
436 436 +++ b/foo
437 437 @@ -1,2 +1,3 @@
438 438 hello world
439 439 +\r (esc)
440 440 goodbye world
441 441
442 442 New line not noticed when space change ignored:
443 443
444 444 $ hg ndiff --ignore-blank-lines --ignore-all-space
445
446 Do not ignore all newlines, only blank lines
447
448 $ printf 'hello \nworld\ngoodbye world\n' > foo
449 $ hg ndiff --ignore-blank-lines
450 diff -r 540c40a65b78 foo
451 --- a/foo
452 +++ b/foo
453 @@ -1,2 +1,3 @@
454 -hello world
455 +hello
456 +world
457 goodbye world
@@ -1,227 +1,279 b''
1 1 Create a repo with some stuff in it:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo a > a
6 6 $ echo a > d
7 7 $ echo a > e
8 8 $ hg ci -qAm0
9 9 $ echo b > a
10 10 $ hg ci -m1 -u bar
11 11 $ hg mv a b
12 12 $ hg ci -m2
13 13 $ hg cp b c
14 14 $ hg ci -m3 -u baz
15 15 $ echo b > d
16 16 $ echo f > e
17 17 $ hg ci -m4
18 18 $ hg up -q 3
19 19 $ echo b > e
20 20 $ hg branch -q stable
21 21 $ hg ci -m5
22 22 $ hg merge -q default --tool internal:local
23 23 $ hg branch -q default
24 24 $ hg ci -m6
25 25
26 26 Need to specify a rev:
27 27
28 28 $ hg graft
29 29 abort: no revisions specified
30 30 [255]
31 31
32 32 Can't graft ancestor:
33 33
34 34 $ hg graft 1 2
35 35 skipping ancestor revision 1
36 36 skipping ancestor revision 2
37 37 [255]
38 38
39 39 Can't graft with dirty wd:
40 40
41 41 $ hg up -q 0
42 42 $ echo foo > a
43 43 $ hg graft 1
44 44 abort: outstanding uncommitted changes
45 45 [255]
46 46 $ hg revert a
47 47
48 48 Graft a rename:
49 49
50 50 $ hg graft 2 -u foo
51 51 grafting revision 2
52 52 merging a and b to b
53 53 $ hg export tip --git
54 54 # HG changeset patch
55 55 # User foo
56 56 # Date 0 0
57 57 # Node ID d2e44c99fd3f31c176ea4efb9eca9f6306c81756
58 58 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
59 59 2
60 60
61 61 diff --git a/a b/b
62 62 rename from a
63 63 rename to b
64 64 --- a/a
65 65 +++ b/b
66 66 @@ -1,1 +1,1 @@
67 67 -a
68 68 +b
69 69
70 70 Look for extra:source
71 71
72 72 $ hg log --debug -r tip
73 73 changeset: 7:d2e44c99fd3f31c176ea4efb9eca9f6306c81756
74 74 tag: tip
75 75 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
76 76 parent: -1:0000000000000000000000000000000000000000
77 77 manifest: 7:5d59766436fd8fbcd38e7bebef0f6eaf3eebe637
78 78 user: foo
79 79 date: Thu Jan 01 00:00:00 1970 +0000
80 80 files+: b
81 81 files-: a
82 82 extra: branch=default
83 83 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
84 84 description:
85 85 2
86 86
87 87
88 88
89 89 Graft out of order, skipping a merge and a duplicate
90 90
91 91 $ hg graft 1 5 4 3 'merge()' 2 --debug
92 92 skipping ungraftable merge revision 6
93 93 scanning for duplicate grafts
94 94 skipping already grafted revision 2
95 95 grafting revision 1
96 96 searching for copies back to rev 1
97 97 unmatched files in local:
98 98 a.orig
99 99 b
100 100 all copies found (* = to merge, ! = divergent):
101 101 b -> a *
102 102 checking for directory renames
103 103 resolving manifests
104 104 overwrite False partial False
105 105 ancestor 68795b066622 local d2e44c99fd3f+ remote 5d205f8b35b6
106 106 b: local copied/moved to a -> m
107 107 preserving b for resolve of b
108 108 updating: b 1/1 files (100.00%)
109 109 searching for copies back to rev 1
110 110 unmatched files in local:
111 111 a
112 112 unmatched files in other:
113 113 b
114 114 all copies found (* = to merge, ! = divergent):
115 115 b -> a *
116 116 checking for directory renames
117 117 b
118 118 b: searching for copy revision for a
119 119 b: copy a:b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
120 120 grafting revision 5
121 121 searching for copies back to rev 1
122 122 unmatched files in local:
123 123 a.orig
124 124 resolving manifests
125 125 overwrite False partial False
126 126 ancestor 4c60f11aa304 local 6f5ea6ac8b70+ remote 97f8bfe72746
127 127 e: remote is newer -> g
128 128 updating: e 1/1 files (100.00%)
129 129 getting e
130 130 searching for copies back to rev 1
131 131 unmatched files in local:
132 132 c
133 133 all copies found (* = to merge, ! = divergent):
134 134 c -> b *
135 135 checking for directory renames
136 136 e
137 137 grafting revision 4
138 138 searching for copies back to rev 1
139 139 unmatched files in local:
140 140 a.orig
141 141 resolving manifests
142 142 overwrite False partial False
143 143 ancestor 4c60f11aa304 local 77eb504366ab+ remote 9c233e8e184d
144 144 e: versions differ -> m
145 145 d: remote is newer -> g
146 146 preserving e for resolve of e
147 147 updating: d 1/2 files (50.00%)
148 148 getting d
149 149 updating: e 2/2 files (100.00%)
150 150 picked tool 'internal:merge' for e (binary False symlink False)
151 151 merging e
152 152 my e@77eb504366ab+ other e@9c233e8e184d ancestor e@68795b066622
153 153 warning: conflicts during merge.
154 154 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
155 155 searching for copies back to rev 1
156 156 unmatched files in local:
157 157 c
158 158 all copies found (* = to merge, ! = divergent):
159 159 c -> b *
160 160 checking for directory renames
161 161 abort: unresolved conflicts, can't continue
162 162 (use hg resolve and hg graft --continue)
163 163 [255]
164 164
165 165 Continue without resolve should fail:
166 166
167 167 $ hg graft -c
168 168 grafting revision 4
169 169 abort: unresolved merge conflicts (see hg help resolve)
170 170 [255]
171 171
172 172 Fix up:
173 173
174 174 $ echo b > e
175 175 $ hg resolve -m e
176 176
177 177 Continue with a revision should fail:
178 178
179 179 $ hg graft -c 6
180 180 abort: can't specify --continue and revisions
181 181 [255]
182 182
183 183 Continue for real, clobber usernames
184 184
185 185 $ hg graft -c -U
186 186 grafting revision 4
187 187 grafting revision 3
188 188
189 189 Compare with original:
190 190
191 191 $ hg diff -r 6
192 192 $ hg status --rev 0:. -C
193 193 M d
194 194 M e
195 195 A b
196 196 a
197 197 A c
198 198 a
199 199 R a
200 200
201 201 View graph:
202 202
203 $ hg --config extensions.graphlog= log -G --template '{author}@rev: {desc}\n'
204 @ test@rev: 3
203 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}: {desc}\n'
204 @ test@11: 3
205 205 |
206 o test@rev: 4
206 o test@10: 4
207 207 |
208 o test@rev: 5
208 o test@9: 5
209 209 |
210 o bar@rev: 1
210 o bar@8: 1
211 211 |
212 o foo@rev: 2
212 o foo@7: 2
213 213 |
214 | o test@rev: 6
214 | o test@6: 6
215 215 | |\
216 | | o test@rev: 5
216 | | o test@5: 5
217 217 | | |
218 | o | test@rev: 4
218 | o | test@4: 4
219 219 | |/
220 | o baz@rev: 3
220 | o baz@3: 3
221 | |
222 | o test@2: 2
221 223 | |
222 | o test@rev: 2
223 | |
224 | o bar@rev: 1
224 | o bar@1: 1
225 225 |/
226 o test@rev: 0
226 o test@0: 0
227 227
228 Graft again onto another branch should preserve the original source
229 $ hg up -q 0
230 $ echo 'g'>g
231 $ hg add g
232 $ hg ci -m 7
233 created new head
234 $ hg graft 7
235 grafting revision 7
236
237 $ hg log -r 7 --template '{rev}:{node}\n'
238 7:d2e44c99fd3f31c176ea4efb9eca9f6306c81756
239 $ hg log -r 2 --template '{rev}:{node}\n'
240 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
241
242 $ hg log --debug -r tip
243 changeset: 13:39bb1d13572759bd1e6fc874fed1b12ece047a18
244 tag: tip
245 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
246 parent: -1:0000000000000000000000000000000000000000
247 manifest: 13:0780e055d8f4cd12eadd5a2719481648f336f7a9
248 user: foo
249 date: Thu Jan 01 00:00:00 1970 +0000
250 files+: b
251 files-: a
252 extra: branch=default
253 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
254 description:
255 2
256
257
258 Disallow grafting an already grafted cset onto its original branch
259 $ hg up -q 6
260 $ hg graft 7
261 skipping already grafted revision 7 (was grafted from 2)
262 [255]
263
264 Disallow grafting already grafted csets with the same origin onto each other
265 $ hg up -q 13
266 $ hg graft 2
267 skipping already grafted revision 2
268 [255]
269 $ hg graft 7
270 skipping already grafted revision 7 (same origin 2)
271 [255]
272
273 $ hg up -q 7
274 $ hg graft 2
275 skipping already grafted revision 2
276 [255]
277 $ hg graft tip
278 skipping already grafted revision 13 (same origin 2)
279 [255]
@@ -1,238 +1,246 b''
1 1 import os
2 2
3 3 def check(a, b):
4 4 if a != b:
5 5 print (a, b)
6 6
7 7 def cert(cn):
8 8 return dict(subject=((('commonName', cn),),))
9 9
10 10 from mercurial.sslutil import _verifycert
11 11
12 12 # Test non-wildcard certificates
13 13 check(_verifycert(cert('example.com'), 'example.com'),
14 14 None)
15 15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 16 'certificate is for example.com')
17 17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 18 'certificate is for www.example.com')
19 19
20 20 # Test wildcard certificates
21 21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 22 None)
23 23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 24 'certificate is for *.example.com')
25 25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 26 'certificate is for *.example.com')
27 27
28 28 # Test subjectAltName
29 29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 30 'subjectAltName': (('DNS', '*.example.net'),
31 31 ('DNS', 'example.net'))}
32 32 check(_verifycert(san_cert, 'example.net'),
33 33 None)
34 34 check(_verifycert(san_cert, 'foo.example.net'),
35 35 None)
36 36 # no fallback to subject commonName when subjectAltName has DNS
37 37 check(_verifycert(san_cert, 'example.com'),
38 38 'certificate is for *.example.net, example.net')
39 39 # fallback to subject commonName when no DNS in subjectAltName
40 40 san_cert = {'subject': ((('commonName', 'example.com'),),),
41 41 'subjectAltName': (('IP Address', '8.8.8.8'),)}
42 42 check(_verifycert(san_cert, 'example.com'), None)
43 43
44 44 # Avoid some pitfalls
45 45 check(_verifycert(cert('*.foo'), 'foo'),
46 46 'certificate is for *.foo')
47 47 check(_verifycert(cert('*o'), 'foo'),
48 48 'certificate is for *o')
49 49
50 50 check(_verifycert({'subject': ()},
51 51 'example.com'),
52 52 'no commonName or subjectAltName found in certificate')
53 53 check(_verifycert(None, 'example.com'),
54 54 'no certificate received')
55 55
56 56 # Unicode (IDN) certname isn't supported
57 57 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
58 58 'IDN in certificate not supported')
59 59
60 60 import doctest
61 61
62 62 def test_url():
63 63 """
64 64 >>> from mercurial.util import url
65 65
66 66 This tests for edge cases in url.URL's parsing algorithm. Most of
67 67 these aren't useful for documentation purposes, so they aren't
68 68 part of the class's doc tests.
69 69
70 70 Query strings and fragments:
71 71
72 72 >>> url('http://host/a?b#c')
73 73 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
74 74 >>> url('http://host/a?')
75 75 <url scheme: 'http', host: 'host', path: 'a'>
76 76 >>> url('http://host/a#b#c')
77 77 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
78 78 >>> url('http://host/a#b?c')
79 79 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
80 80 >>> url('http://host/?a#b')
81 81 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
82 82 >>> url('http://host/?a#b', parsequery=False)
83 83 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
84 84 >>> url('http://host/?a#b', parsefragment=False)
85 85 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
86 86 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
87 87 <url scheme: 'http', host: 'host', path: '?a#b'>
88 88
89 89 IPv6 addresses:
90 90
91 91 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
92 92 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
93 93 query: 'objectClass?one'>
94 94 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
95 95 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
96 96 port: '80', path: 'c=GB', query: 'objectClass?one'>
97 97
98 98 Missing scheme, host, etc.:
99 99
100 100 >>> url('://192.0.2.16:80/')
101 101 <url path: '://192.0.2.16:80/'>
102 102 >>> url('http://mercurial.selenic.com')
103 103 <url scheme: 'http', host: 'mercurial.selenic.com'>
104 104 >>> url('/foo')
105 105 <url path: '/foo'>
106 106 >>> url('bundle:/foo')
107 107 <url scheme: 'bundle', path: '/foo'>
108 108 >>> url('a?b#c')
109 109 <url path: 'a?b', fragment: 'c'>
110 110 >>> url('http://x.com?arg=/foo')
111 111 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
112 112 >>> url('http://joe:xxx@/foo')
113 113 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
114 114
115 115 Just a scheme and a path:
116 116
117 117 >>> url('mailto:John.Doe@example.com')
118 118 <url scheme: 'mailto', path: 'John.Doe@example.com'>
119 119 >>> url('a:b:c:d')
120 120 <url path: 'a:b:c:d'>
121 121 >>> url('aa:bb:cc:dd')
122 122 <url scheme: 'aa', path: 'bb:cc:dd'>
123 123
124 124 SSH examples:
125 125
126 126 >>> url('ssh://joe@host//home/joe')
127 127 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
128 128 >>> url('ssh://joe:xxx@host/src')
129 129 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
130 130 >>> url('ssh://joe:xxx@host')
131 131 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
132 132 >>> url('ssh://joe@host')
133 133 <url scheme: 'ssh', user: 'joe', host: 'host'>
134 134 >>> url('ssh://host')
135 135 <url scheme: 'ssh', host: 'host'>
136 136 >>> url('ssh://')
137 137 <url scheme: 'ssh'>
138 138 >>> url('ssh:')
139 139 <url scheme: 'ssh'>
140 140
141 141 Non-numeric port:
142 142
143 143 >>> url('http://example.com:dd')
144 144 <url scheme: 'http', host: 'example.com', port: 'dd'>
145 145 >>> url('ssh://joe:xxx@host:ssh/foo')
146 146 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
147 147 path: 'foo'>
148 148
149 149 Bad authentication credentials:
150 150
151 151 >>> url('http://joe@joeville:123@4:@host/a?b#c')
152 152 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
153 153 host: 'host', path: 'a', query: 'b', fragment: 'c'>
154 154 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
155 155 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
156 156 >>> url('http://!*#?@!*#?:@host/a?b#c')
157 157 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
158 158 >>> url('http://!*@:!*@@host/a?b#c')
159 159 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
160 160 path: 'a', query: 'b', fragment: 'c'>
161 161
162 162 File paths:
163 163
164 164 >>> url('a/b/c/d.g.f')
165 165 <url path: 'a/b/c/d.g.f'>
166 166 >>> url('/x///z/y/')
167 167 <url path: '/x///z/y/'>
168 168 >>> url('/foo:bar')
169 169 <url path: '/foo:bar'>
170 170 >>> url('\\\\foo:bar')
171 171 <url path: '\\\\foo:bar'>
172 172 >>> url('./foo:bar')
173 173 <url path: './foo:bar'>
174 174
175 175 Non-localhost file URL:
176 176
177 177 >>> u = url('file://mercurial.selenic.com/foo')
178 178 Traceback (most recent call last):
179 179 File "<stdin>", line 1, in ?
180 180 Abort: file:// URLs can only refer to localhost
181 181
182 182 Empty URL:
183 183
184 184 >>> u = url('')
185 185 >>> u
186 186 <url path: ''>
187 187 >>> str(u)
188 188 ''
189 189
190 190 Empty path with query string:
191 191
192 192 >>> str(url('http://foo/?bar'))
193 193 'http://foo/?bar'
194 194
195 195 Invalid path:
196 196
197 197 >>> u = url('http://foo/bar')
198 198 >>> u.path = 'bar'
199 199 >>> str(u)
200 200 'http://foo/bar'
201 201
202 202 >>> u = url('file:/foo/bar/baz')
203 203 >>> u
204 204 <url scheme: 'file', path: '/foo/bar/baz'>
205 205 >>> str(u)
206 206 'file:///foo/bar/baz'
207 207 >>> u.localpath()
208 208 '/foo/bar/baz'
209 209
210 210 >>> u = url('file:///foo/bar/baz')
211 211 >>> u
212 212 <url scheme: 'file', path: '/foo/bar/baz'>
213 213 >>> str(u)
214 214 'file:///foo/bar/baz'
215 215 >>> u.localpath()
216 216 '/foo/bar/baz'
217 217
218 218 >>> u = url('file:///f:oo/bar/baz')
219 219 >>> u
220 220 <url scheme: 'file', path: 'f:oo/bar/baz'>
221 221 >>> str(u)
222 222 'file:f:oo/bar/baz'
223 223 >>> u.localpath()
224 224 'f:oo/bar/baz'
225 225
226 >>> u = url('file://localhost/f:oo/bar/baz')
227 >>> u
228 <url scheme: 'file', host: 'localhost', path: 'f:oo/bar/baz'>
229 >>> str(u)
230 'file://localhost/f:oo/bar/baz'
231 >>> u.localpath()
232 'f:oo/bar/baz'
233
226 234 >>> u = url('file:foo/bar/baz')
227 235 >>> u
228 236 <url scheme: 'file', path: 'foo/bar/baz'>
229 237 >>> str(u)
230 238 'file:foo/bar/baz'
231 239 >>> u.localpath()
232 240 'foo/bar/baz'
233 241 """
234 242
235 243 if 'TERM' in os.environ:
236 244 del os.environ['TERM']
237 245
238 246 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
General Comments 0
You need to be logged in to leave comments. Login now