##// END OF EJS Templates
merge with stable
Matt Mackall -
r16603:ddd49967 merge default
parent child Browse files
Show More
@@ -1,166 +1,168 b''
1 1 # Copyright 2011 Fog Creek Software
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 import os
7 7 import urllib2
8 8
9 9 from mercurial import error, httprepo, util, wireproto
10 10 from mercurial.i18n import _
11 11
12 12 import lfutil
13 13
14 14 LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.'
15 15 '\n\nPlease enable it in your Mercurial config '
16 16 'file.\n')
17 17
18 18 def putlfile(repo, proto, sha):
19 19 '''Put a largefile into a repository's local store and into the
20 20 user cache.'''
21 21 proto.redirect()
22 22
23 tmpfp = util.atomictempfile(lfutil.storepath(repo, sha),
24 createmode=repo.store.createmode)
23 path = lfutil.storepath(repo, sha)
24 util.makedirs(os.path.dirname(path))
25 tmpfp = util.atomictempfile(path, createmode=repo.store.createmode)
26
25 27 try:
26 28 try:
27 29 proto.getfile(tmpfp)
28 30 tmpfp._fp.seek(0)
29 31 if sha != lfutil.hexsha1(tmpfp._fp):
30 32 raise IOError(0, _('largefile contents do not match hash'))
31 33 tmpfp.close()
32 34 lfutil.linktousercache(repo, sha)
33 35 except IOError, e:
34 36 repo.ui.warn(_('largefiles: failed to put %s into store: %s') %
35 37 (sha, e.strerror))
36 38 return wireproto.pushres(1)
37 39 finally:
38 40 tmpfp.discard()
39 41
40 42 return wireproto.pushres(0)
41 43
42 44 def getlfile(repo, proto, sha):
43 45 '''Retrieve a largefile from the repository-local cache or system
44 46 cache.'''
45 47 filename = lfutil.findfile(repo, sha)
46 48 if not filename:
47 49 raise util.Abort(_('requested largefile %s not present in cache') % sha)
48 50 f = open(filename, 'rb')
49 51 length = os.fstat(f.fileno())[6]
50 52
51 53 # Since we can't set an HTTP content-length header here, and
52 54 # Mercurial core provides no way to give the length of a streamres
53 55 # (and reading the entire file into RAM would be ill-advised), we
54 56 # just send the length on the first line of the response, like the
55 57 # ssh proto does for string responses.
56 58 def generator():
57 59 yield '%d\n' % length
58 60 for chunk in f:
59 61 yield chunk
60 62 return wireproto.streamres(generator())
61 63
62 64 def statlfile(repo, proto, sha):
63 65 '''Return '2\n' if the largefile is missing, '1\n' if it has a
64 66 mismatched checksum, or '0\n' if it is in good condition'''
65 67 filename = lfutil.findfile(repo, sha)
66 68 if not filename:
67 69 return '2\n'
68 70 fd = None
69 71 try:
70 72 fd = open(filename, 'rb')
71 73 return lfutil.hexsha1(fd) == sha and '0\n' or '1\n'
72 74 finally:
73 75 if fd:
74 76 fd.close()
75 77
76 78 def wirereposetup(ui, repo):
77 79 class lfileswirerepository(repo.__class__):
78 80 def putlfile(self, sha, fd):
79 81 # unfortunately, httprepository._callpush tries to convert its
80 82 # input file-like into a bundle before sending it, so we can't use
81 83 # it ...
82 84 if issubclass(self.__class__, httprepo.httprepository):
83 85 res = None
84 86 try:
85 87 res = self._call('putlfile', data=fd, sha=sha,
86 88 headers={'content-type':'application/mercurial-0.1'})
87 89 d, output = res.split('\n', 1)
88 90 for l in output.splitlines(True):
89 91 self.ui.warn(_('remote: '), l, '\n')
90 92 return int(d)
91 93 except (ValueError, urllib2.HTTPError):
92 94 self.ui.warn(_('unexpected putlfile response: %s') % res)
93 95 return 1
94 96 # ... but we can't use sshrepository._call because the data=
95 97 # argument won't get sent, and _callpush does exactly what we want
96 98 # in this case: send the data straight through
97 99 else:
98 100 try:
99 101 ret, output = self._callpush("putlfile", fd, sha=sha)
100 102 if ret == "":
101 103 raise error.ResponseError(_('putlfile failed:'),
102 104 output)
103 105 return int(ret)
104 106 except IOError:
105 107 return 1
106 108 except ValueError:
107 109 raise error.ResponseError(
108 110 _('putlfile failed (unexpected response):'), ret)
109 111
110 112 def getlfile(self, sha):
111 113 stream = self._callstream("getlfile", sha=sha)
112 114 length = stream.readline()
113 115 try:
114 116 length = int(length)
115 117 except ValueError:
116 118 self._abort(error.ResponseError(_("unexpected response:"),
117 119 length))
118 120 return (length, stream)
119 121
120 122 def statlfile(self, sha):
121 123 try:
122 124 return int(self._call("statlfile", sha=sha))
123 125 except (ValueError, urllib2.HTTPError):
124 126 # If the server returns anything but an integer followed by a
125 127 # newline, newline, it's not speaking our language; if we get
126 128 # an HTTP error, we can't be sure the largefile is present;
127 129 # either way, consider it missing.
128 130 return 2
129 131
130 132 repo.__class__ = lfileswirerepository
131 133
132 134 # advertise the largefiles=serve capability
133 135 def capabilities(repo, proto):
134 136 return capabilitiesorig(repo, proto) + ' largefiles=serve'
135 137
136 138 # duplicate what Mercurial's new out-of-band errors mechanism does, because
137 139 # clients old and new alike both handle it well
138 140 def webprotorefuseclient(self, message):
139 141 self.req.header([('Content-Type', 'application/hg-error')])
140 142 return message
141 143
142 144 def sshprotorefuseclient(self, message):
143 145 self.ui.write_err('%s\n-\n' % message)
144 146 self.fout.write('\n')
145 147 self.fout.flush()
146 148
147 149 return ''
148 150
149 151 def heads(repo, proto):
150 152 if lfutil.islfilesrepo(repo):
151 153 return wireproto.ooberror(LARGEFILES_REQUIRED_MSG)
152 154 return wireproto.heads(repo, proto)
153 155
154 156 def sshrepocallstream(self, cmd, **args):
155 157 if cmd == 'heads' and self.capable('largefiles'):
156 158 cmd = 'lheads'
157 159 if cmd == 'batch' and self.capable('largefiles'):
158 160 args['cmds'] = args['cmds'].replace('heads ', 'lheads ')
159 161 return ssholdcallstream(self, cmd, **args)
160 162
161 163 def httprepocallstream(self, cmd, **args):
162 164 if cmd == 'heads' and self.capable('largefiles'):
163 165 cmd = 'lheads'
164 166 if cmd == 'batch' and self.capable('largefiles'):
165 167 args['cmds'] = args['cmds'].replace('heads ', 'lheads ')
166 168 return httpoldcallstream(self, cmd, **args)
@@ -1,5723 +1,5724 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 merge as mergemod
17 17 import minirst, revset, fileset
18 18 import dagparser, context, simplemerge
19 19 import random, setdiscovery, treediscovery, dagutil, pvec
20 20 import phases
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 diffwsopts = [
110 110 ('w', 'ignore-all-space', None,
111 111 _('ignore white space when comparing lines')),
112 112 ('b', 'ignore-space-change', None,
113 113 _('ignore changes in the amount of white space')),
114 114 ('B', 'ignore-blank-lines', None,
115 115 _('ignore changes whose lines are all blank')),
116 116 ]
117 117
118 118 diffopts2 = [
119 119 ('p', 'show-function', None, _('show which function each change is in')),
120 120 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 121 ] + diffwsopts + [
122 122 ('U', 'unified', '',
123 123 _('number of lines of context to show'), _('NUM')),
124 124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 125 ]
126 126
127 127 mergetoolopts = [
128 128 ('t', 'tool', '', _('specify merge tool')),
129 129 ]
130 130
131 131 similarityopts = [
132 132 ('s', 'similarity', '',
133 133 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 134 ]
135 135
136 136 subrepoopts = [
137 137 ('S', 'subrepos', None,
138 138 _('recurse into subrepositories'))
139 139 ]
140 140
141 141 # Commands start here, listed alphabetically
142 142
143 143 @command('^add',
144 144 walkopts + subrepoopts + dryrunopts,
145 145 _('[OPTION]... [FILE]...'))
146 146 def add(ui, repo, *pats, **opts):
147 147 """add the specified files on the next commit
148 148
149 149 Schedule files to be version controlled and added to the
150 150 repository.
151 151
152 152 The files will be added to the repository at the next commit. To
153 153 undo an add before that, see :hg:`forget`.
154 154
155 155 If no names are given, add all files to the repository.
156 156
157 157 .. container:: verbose
158 158
159 159 An example showing how new (unknown) files are added
160 160 automatically by :hg:`add`::
161 161
162 162 $ ls
163 163 foo.c
164 164 $ hg status
165 165 ? foo.c
166 166 $ hg add
167 167 adding foo.c
168 168 $ hg status
169 169 A foo.c
170 170
171 171 Returns 0 if all files are successfully added.
172 172 """
173 173
174 174 m = scmutil.match(repo[None], pats, opts)
175 175 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 176 opts.get('subrepos'), prefix="", explicitonly=False)
177 177 return rejected and 1 or 0
178 178
179 179 @command('addremove',
180 180 similarityopts + walkopts + dryrunopts,
181 181 _('[OPTION]... [FILE]...'))
182 182 def addremove(ui, repo, *pats, **opts):
183 183 """add all new files, delete all missing files
184 184
185 185 Add all new files and remove all missing files from the
186 186 repository.
187 187
188 188 New files are ignored if they match any of the patterns in
189 189 ``.hgignore``. As with add, these changes take effect at the next
190 190 commit.
191 191
192 192 Use the -s/--similarity option to detect renamed files. With a
193 193 parameter greater than 0, this compares every removed file with
194 194 every added file and records those similar enough as renames. This
195 195 option takes a percentage between 0 (disabled) and 100 (files must
196 196 be identical) as its parameter. Detecting renamed files this way
197 197 can be expensive. After using this option, :hg:`status -C` can be
198 198 used to check which files were identified as moved or renamed.
199 199
200 200 Returns 0 if all files are successfully added.
201 201 """
202 202 try:
203 203 sim = float(opts.get('similarity') or 100)
204 204 except ValueError:
205 205 raise util.Abort(_('similarity must be a number'))
206 206 if sim < 0 or sim > 100:
207 207 raise util.Abort(_('similarity must be between 0 and 100'))
208 208 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
209 209
210 210 @command('^annotate|blame',
211 211 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
212 212 ('', 'follow', None,
213 213 _('follow copies/renames and list the filename (DEPRECATED)')),
214 214 ('', 'no-follow', None, _("don't follow copies and renames")),
215 215 ('a', 'text', None, _('treat all files as text')),
216 216 ('u', 'user', None, _('list the author (long with -v)')),
217 217 ('f', 'file', None, _('list the filename')),
218 218 ('d', 'date', None, _('list the date (short with -q)')),
219 219 ('n', 'number', None, _('list the revision number (default)')),
220 220 ('c', 'changeset', None, _('list the changeset')),
221 221 ('l', 'line-number', None, _('show line number at the first appearance'))
222 222 ] + diffwsopts + walkopts,
223 223 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
224 224 def annotate(ui, repo, *pats, **opts):
225 225 """show changeset information by line for each file
226 226
227 227 List changes in files, showing the revision id responsible for
228 228 each line
229 229
230 230 This command is useful for discovering when a change was made and
231 231 by whom.
232 232
233 233 Without the -a/--text option, annotate will avoid processing files
234 234 it detects as binary. With -a, annotate will annotate the file
235 235 anyway, although the results will probably be neither useful
236 236 nor desirable.
237 237
238 238 Returns 0 on success.
239 239 """
240 240 if opts.get('follow'):
241 241 # --follow is deprecated and now just an alias for -f/--file
242 242 # to mimic the behavior of Mercurial before version 1.5
243 243 opts['file'] = True
244 244
245 245 datefunc = ui.quiet and util.shortdate or util.datestr
246 246 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
247 247
248 248 if not pats:
249 249 raise util.Abort(_('at least one filename or pattern is required'))
250 250
251 251 hexfn = ui.debugflag and hex or short
252 252
253 253 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
254 254 ('number', ' ', lambda x: str(x[0].rev())),
255 255 ('changeset', ' ', lambda x: hexfn(x[0].node())),
256 256 ('date', ' ', getdate),
257 257 ('file', ' ', lambda x: x[0].path()),
258 258 ('line_number', ':', lambda x: str(x[1])),
259 259 ]
260 260
261 261 if (not opts.get('user') and not opts.get('changeset')
262 262 and not opts.get('date') and not opts.get('file')):
263 263 opts['number'] = True
264 264
265 265 linenumber = opts.get('line_number') is not None
266 266 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
267 267 raise util.Abort(_('at least one of -n/-c is required for -l'))
268 268
269 269 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
270 270 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
271 271
272 272 def bad(x, y):
273 273 raise util.Abort("%s: %s" % (x, y))
274 274
275 275 ctx = scmutil.revsingle(repo, opts.get('rev'))
276 276 m = scmutil.match(ctx, pats, opts)
277 277 m.bad = bad
278 278 follow = not opts.get('no_follow')
279 279 diffopts = patch.diffopts(ui, opts, section='annotate')
280 280 for abs in ctx.walk(m):
281 281 fctx = ctx[abs]
282 282 if not opts.get('text') and util.binary(fctx.data()):
283 283 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
284 284 continue
285 285
286 286 lines = fctx.annotate(follow=follow, linenumber=linenumber,
287 287 diffopts=diffopts)
288 288 pieces = []
289 289
290 290 for f, sep in funcmap:
291 291 l = [f(n) for n, dummy in lines]
292 292 if l:
293 293 sized = [(x, encoding.colwidth(x)) for x in l]
294 294 ml = max([w for x, w in sized])
295 295 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
296 296 for x, w in sized])
297 297
298 298 if pieces:
299 299 for p, l in zip(zip(*pieces), lines):
300 300 ui.write("%s: %s" % ("".join(p), l[1]))
301 301
302 302 if lines and not lines[-1][1].endswith('\n'):
303 303 ui.write('\n')
304 304
305 305 @command('archive',
306 306 [('', 'no-decode', None, _('do not pass files through decoders')),
307 307 ('p', 'prefix', '', _('directory prefix for files in archive'),
308 308 _('PREFIX')),
309 309 ('r', 'rev', '', _('revision to distribute'), _('REV')),
310 310 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
311 311 ] + subrepoopts + walkopts,
312 312 _('[OPTION]... DEST'))
313 313 def archive(ui, repo, dest, **opts):
314 314 '''create an unversioned archive of a repository revision
315 315
316 316 By default, the revision used is the parent of the working
317 317 directory; use -r/--rev to specify a different revision.
318 318
319 319 The archive type is automatically detected based on file
320 320 extension (or override using -t/--type).
321 321
322 322 .. container:: verbose
323 323
324 324 Examples:
325 325
326 326 - create a zip file containing the 1.0 release::
327 327
328 328 hg archive -r 1.0 project-1.0.zip
329 329
330 330 - create a tarball excluding .hg files::
331 331
332 332 hg archive project.tar.gz -X ".hg*"
333 333
334 334 Valid types are:
335 335
336 336 :``files``: a directory full of files (default)
337 337 :``tar``: tar archive, uncompressed
338 338 :``tbz2``: tar archive, compressed using bzip2
339 339 :``tgz``: tar archive, compressed using gzip
340 340 :``uzip``: zip archive, uncompressed
341 341 :``zip``: zip archive, compressed using deflate
342 342
343 343 The exact name of the destination archive or directory is given
344 344 using a format string; see :hg:`help export` for details.
345 345
346 346 Each member added to an archive file has a directory prefix
347 347 prepended. Use -p/--prefix to specify a format string for the
348 348 prefix. The default is the basename of the archive, with suffixes
349 349 removed.
350 350
351 351 Returns 0 on success.
352 352 '''
353 353
354 354 ctx = scmutil.revsingle(repo, opts.get('rev'))
355 355 if not ctx:
356 356 raise util.Abort(_('no working directory: please specify a revision'))
357 357 node = ctx.node()
358 358 dest = cmdutil.makefilename(repo, dest, node)
359 359 if os.path.realpath(dest) == repo.root:
360 360 raise util.Abort(_('repository root cannot be destination'))
361 361
362 362 kind = opts.get('type') or archival.guesskind(dest) or 'files'
363 363 prefix = opts.get('prefix')
364 364
365 365 if dest == '-':
366 366 if kind == 'files':
367 367 raise util.Abort(_('cannot archive plain files to stdout'))
368 368 dest = cmdutil.makefileobj(repo, dest)
369 369 if not prefix:
370 370 prefix = os.path.basename(repo.root) + '-%h'
371 371
372 372 prefix = cmdutil.makefilename(repo, prefix, node)
373 373 matchfn = scmutil.match(ctx, [], opts)
374 374 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
375 375 matchfn, prefix, subrepos=opts.get('subrepos'))
376 376
377 377 @command('backout',
378 378 [('', 'merge', None, _('merge with old dirstate parent after backout')),
379 379 ('', 'parent', '',
380 380 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
381 381 ('r', 'rev', '', _('revision to backout'), _('REV')),
382 382 ] + mergetoolopts + walkopts + commitopts + commitopts2,
383 383 _('[OPTION]... [-r] REV'))
384 384 def backout(ui, repo, node=None, rev=None, **opts):
385 385 '''reverse effect of earlier changeset
386 386
387 387 Prepare a new changeset with the effect of REV undone in the
388 388 current working directory.
389 389
390 390 If REV is the parent of the working directory, then this new changeset
391 391 is committed automatically. Otherwise, hg needs to merge the
392 392 changes and the merged result is left uncommitted.
393 393
394 394 .. note::
395 395 backout cannot be used to fix either an unwanted or
396 396 incorrect merge.
397 397
398 398 .. container:: verbose
399 399
400 400 By default, the pending changeset will have one parent,
401 401 maintaining a linear history. With --merge, the pending
402 402 changeset will instead have two parents: the old parent of the
403 403 working directory and a new child of REV that simply undoes REV.
404 404
405 405 Before version 1.7, the behavior without --merge was equivalent
406 406 to specifying --merge followed by :hg:`update --clean .` to
407 407 cancel the merge and leave the child of REV as a head to be
408 408 merged separately.
409 409
410 410 See :hg:`help dates` for a list of formats valid for -d/--date.
411 411
412 412 Returns 0 on success.
413 413 '''
414 414 if rev and node:
415 415 raise util.Abort(_("please specify just one revision"))
416 416
417 417 if not rev:
418 418 rev = node
419 419
420 420 if not rev:
421 421 raise util.Abort(_("please specify a revision to backout"))
422 422
423 423 date = opts.get('date')
424 424 if date:
425 425 opts['date'] = util.parsedate(date)
426 426
427 427 cmdutil.bailifchanged(repo)
428 428 node = scmutil.revsingle(repo, rev).node()
429 429
430 430 op1, op2 = repo.dirstate.parents()
431 431 a = repo.changelog.ancestor(op1, node)
432 432 if a != node:
433 433 raise util.Abort(_('cannot backout change on a different branch'))
434 434
435 435 p1, p2 = repo.changelog.parents(node)
436 436 if p1 == nullid:
437 437 raise util.Abort(_('cannot backout a change with no parents'))
438 438 if p2 != nullid:
439 439 if not opts.get('parent'):
440 440 raise util.Abort(_('cannot backout a merge changeset'))
441 441 p = repo.lookup(opts['parent'])
442 442 if p not in (p1, p2):
443 443 raise util.Abort(_('%s is not a parent of %s') %
444 444 (short(p), short(node)))
445 445 parent = p
446 446 else:
447 447 if opts.get('parent'):
448 448 raise util.Abort(_('cannot use --parent on non-merge changeset'))
449 449 parent = p1
450 450
451 451 # the backout should appear on the same branch
452 452 wlock = repo.wlock()
453 453 try:
454 454 branch = repo.dirstate.branch()
455 455 hg.clean(repo, node, show_stats=False)
456 456 repo.dirstate.setbranch(branch)
457 457 revert_opts = opts.copy()
458 458 revert_opts['date'] = None
459 459 revert_opts['all'] = True
460 460 revert_opts['rev'] = hex(parent)
461 461 revert_opts['no_backup'] = None
462 462 revert(ui, repo, **revert_opts)
463 463 if not opts.get('merge') and op1 != node:
464 464 try:
465 465 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
466 466 return hg.update(repo, op1)
467 467 finally:
468 468 ui.setconfig('ui', 'forcemerge', '')
469 469
470 470 commit_opts = opts.copy()
471 471 commit_opts['addremove'] = False
472 472 if not commit_opts['message'] and not commit_opts['logfile']:
473 473 # we don't translate commit messages
474 474 commit_opts['message'] = "Backed out changeset %s" % short(node)
475 475 commit_opts['force_editor'] = True
476 476 commit(ui, repo, **commit_opts)
477 477 def nice(node):
478 478 return '%d:%s' % (repo.changelog.rev(node), short(node))
479 479 ui.status(_('changeset %s backs out changeset %s\n') %
480 480 (nice(repo.changelog.tip()), nice(node)))
481 481 if opts.get('merge') and op1 != node:
482 482 hg.clean(repo, op1, show_stats=False)
483 483 ui.status(_('merging with changeset %s\n')
484 484 % nice(repo.changelog.tip()))
485 485 try:
486 486 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
487 487 return hg.merge(repo, hex(repo.changelog.tip()))
488 488 finally:
489 489 ui.setconfig('ui', 'forcemerge', '')
490 490 finally:
491 491 wlock.release()
492 492 return 0
493 493
494 494 @command('bisect',
495 495 [('r', 'reset', False, _('reset bisect state')),
496 496 ('g', 'good', False, _('mark changeset good')),
497 497 ('b', 'bad', False, _('mark changeset bad')),
498 498 ('s', 'skip', False, _('skip testing changeset')),
499 499 ('e', 'extend', False, _('extend the bisect range')),
500 500 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
501 501 ('U', 'noupdate', False, _('do not update to target'))],
502 502 _("[-gbsr] [-U] [-c CMD] [REV]"))
503 503 def bisect(ui, repo, rev=None, extra=None, command=None,
504 504 reset=None, good=None, bad=None, skip=None, extend=None,
505 505 noupdate=None):
506 506 """subdivision search of changesets
507 507
508 508 This command helps to find changesets which introduce problems. To
509 509 use, mark the earliest changeset you know exhibits the problem as
510 510 bad, then mark the latest changeset which is free from the problem
511 511 as good. Bisect will update your working directory to a revision
512 512 for testing (unless the -U/--noupdate option is specified). Once
513 513 you have performed tests, mark the working directory as good or
514 514 bad, and bisect will either update to another candidate changeset
515 515 or announce that it has found the bad revision.
516 516
517 517 As a shortcut, you can also use the revision argument to mark a
518 518 revision as good or bad without checking it out first.
519 519
520 520 If you supply a command, it will be used for automatic bisection.
521 521 Its exit status will be used to mark revisions as good or bad:
522 522 status 0 means good, 125 means to skip the revision, 127
523 523 (command not found) will abort the bisection, and any other
524 524 non-zero exit status means the revision is bad.
525 525
526 526 .. container:: verbose
527 527
528 528 Some examples:
529 529
530 530 - start a bisection with known bad revision 12, and good revision 34::
531 531
532 532 hg bisect --bad 34
533 533 hg bisect --good 12
534 534
535 535 - advance the current bisection by marking current revision as good or
536 536 bad::
537 537
538 538 hg bisect --good
539 539 hg bisect --bad
540 540
541 541 - mark the current revision, or a known revision, to be skipped (eg. if
542 542 that revision is not usable because of another issue)::
543 543
544 544 hg bisect --skip
545 545 hg bisect --skip 23
546 546
547 547 - forget the current bisection::
548 548
549 549 hg bisect --reset
550 550
551 551 - use 'make && make tests' to automatically find the first broken
552 552 revision::
553 553
554 554 hg bisect --reset
555 555 hg bisect --bad 34
556 556 hg bisect --good 12
557 557 hg bisect --command 'make && make tests'
558 558
559 559 - see all changesets whose states are already known in the current
560 560 bisection::
561 561
562 562 hg log -r "bisect(pruned)"
563 563
564 564 - see all changesets that took part in the current bisection::
565 565
566 566 hg log -r "bisect(range)"
567 567
568 568 - with the graphlog extension, you can even get a nice graph::
569 569
570 570 hg log --graph -r "bisect(range)"
571 571
572 572 See :hg:`help revsets` for more about the `bisect()` keyword.
573 573
574 574 Returns 0 on success.
575 575 """
576 576 def extendbisectrange(nodes, good):
577 577 # bisect is incomplete when it ends on a merge node and
578 578 # one of the parent was not checked.
579 579 parents = repo[nodes[0]].parents()
580 580 if len(parents) > 1:
581 581 side = good and state['bad'] or state['good']
582 582 num = len(set(i.node() for i in parents) & set(side))
583 583 if num == 1:
584 584 return parents[0].ancestor(parents[1])
585 585 return None
586 586
587 587 def print_result(nodes, good):
588 588 displayer = cmdutil.show_changeset(ui, repo, {})
589 589 if len(nodes) == 1:
590 590 # narrowed it down to a single revision
591 591 if good:
592 592 ui.write(_("The first good revision is:\n"))
593 593 else:
594 594 ui.write(_("The first bad revision is:\n"))
595 595 displayer.show(repo[nodes[0]])
596 596 extendnode = extendbisectrange(nodes, good)
597 597 if extendnode is not None:
598 598 ui.write(_('Not all ancestors of this changeset have been'
599 599 ' checked.\nUse bisect --extend to continue the '
600 600 'bisection from\nthe common ancestor, %s.\n')
601 601 % extendnode)
602 602 else:
603 603 # multiple possible revisions
604 604 if good:
605 605 ui.write(_("Due to skipped revisions, the first "
606 606 "good revision could be any of:\n"))
607 607 else:
608 608 ui.write(_("Due to skipped revisions, the first "
609 609 "bad revision could be any of:\n"))
610 610 for n in nodes:
611 611 displayer.show(repo[n])
612 612 displayer.close()
613 613
614 614 def check_state(state, interactive=True):
615 615 if not state['good'] or not state['bad']:
616 616 if (good or bad or skip or reset) and interactive:
617 617 return
618 618 if not state['good']:
619 619 raise util.Abort(_('cannot bisect (no known good revisions)'))
620 620 else:
621 621 raise util.Abort(_('cannot bisect (no known bad revisions)'))
622 622 return True
623 623
624 624 # backward compatibility
625 625 if rev in "good bad reset init".split():
626 626 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
627 627 cmd, rev, extra = rev, extra, None
628 628 if cmd == "good":
629 629 good = True
630 630 elif cmd == "bad":
631 631 bad = True
632 632 else:
633 633 reset = True
634 634 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
635 635 raise util.Abort(_('incompatible arguments'))
636 636
637 637 if reset:
638 638 p = repo.join("bisect.state")
639 639 if os.path.exists(p):
640 640 os.unlink(p)
641 641 return
642 642
643 643 state = hbisect.load_state(repo)
644 644
645 645 if command:
646 646 changesets = 1
647 647 try:
648 648 while changesets:
649 649 # update state
650 hbisect.save_state(repo, state)
650 651 status = util.system(command, out=ui.fout)
651 652 if status == 125:
652 653 transition = "skip"
653 654 elif status == 0:
654 655 transition = "good"
655 656 # status < 0 means process was killed
656 657 elif status == 127:
657 658 raise util.Abort(_("failed to execute %s") % command)
658 659 elif status < 0:
659 660 raise util.Abort(_("%s killed") % command)
660 661 else:
661 662 transition = "bad"
662 663 ctx = scmutil.revsingle(repo, rev)
663 664 rev = None # clear for future iterations
664 665 state[transition].append(ctx.node())
665 666 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
666 667 check_state(state, interactive=False)
667 668 # bisect
668 669 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
669 670 # update to next check
670 671 cmdutil.bailifchanged(repo)
671 672 hg.clean(repo, nodes[0], show_stats=False)
672 673 finally:
673 674 hbisect.save_state(repo, state)
674 675 print_result(nodes, good)
675 676 return
676 677
677 678 # update state
678 679
679 680 if rev:
680 681 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
681 682 else:
682 683 nodes = [repo.lookup('.')]
683 684
684 685 if good or bad or skip:
685 686 if good:
686 687 state['good'] += nodes
687 688 elif bad:
688 689 state['bad'] += nodes
689 690 elif skip:
690 691 state['skip'] += nodes
691 692 hbisect.save_state(repo, state)
692 693
693 694 if not check_state(state):
694 695 return
695 696
696 697 # actually bisect
697 698 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
698 699 if extend:
699 700 if not changesets:
700 701 extendnode = extendbisectrange(nodes, good)
701 702 if extendnode is not None:
702 703 ui.write(_("Extending search to changeset %d:%s\n"
703 704 % (extendnode.rev(), extendnode)))
704 705 if noupdate:
705 706 return
706 707 cmdutil.bailifchanged(repo)
707 708 return hg.clean(repo, extendnode.node())
708 709 raise util.Abort(_("nothing to extend"))
709 710
710 711 if changesets == 0:
711 712 print_result(nodes, good)
712 713 else:
713 714 assert len(nodes) == 1 # only a single node can be tested next
714 715 node = nodes[0]
715 716 # compute the approximate number of remaining tests
716 717 tests, size = 0, 2
717 718 while size <= changesets:
718 719 tests, size = tests + 1, size * 2
719 720 rev = repo.changelog.rev(node)
720 721 ui.write(_("Testing changeset %d:%s "
721 722 "(%d changesets remaining, ~%d tests)\n")
722 723 % (rev, short(node), changesets, tests))
723 724 if not noupdate:
724 725 cmdutil.bailifchanged(repo)
725 726 return hg.clean(repo, node)
726 727
727 728 @command('bookmarks',
728 729 [('f', 'force', False, _('force')),
729 730 ('r', 'rev', '', _('revision'), _('REV')),
730 731 ('d', 'delete', False, _('delete a given bookmark')),
731 732 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
732 733 ('i', 'inactive', False, _('mark a bookmark inactive'))],
733 734 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
734 735 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
735 736 rename=None, inactive=False):
736 737 '''track a line of development with movable markers
737 738
738 739 Bookmarks are pointers to certain commits that move when committing.
739 740 Bookmarks are local. They can be renamed, copied and deleted. It is
740 741 possible to use :hg:`merge NAME` to merge from a given bookmark, and
741 742 :hg:`update NAME` to update to a given bookmark.
742 743
743 744 You can use :hg:`bookmark NAME` to set a bookmark on the working
744 745 directory's parent revision with the given name. If you specify
745 746 a revision using -r REV (where REV may be an existing bookmark),
746 747 the bookmark is assigned to that revision.
747 748
748 749 Bookmarks can be pushed and pulled between repositories (see :hg:`help
749 750 push` and :hg:`help pull`). This requires both the local and remote
750 751 repositories to support bookmarks. For versions prior to 1.8, this means
751 752 the bookmarks extension must be enabled.
752 753
753 754 With -i/--inactive, the new bookmark will not be made the active
754 755 bookmark. If -r/--rev is given, the new bookmark will not be made
755 756 active even if -i/--inactive is not given. If no NAME is given, the
756 757 current active bookmark will be marked inactive.
757 758 '''
758 759 hexfn = ui.debugflag and hex or short
759 760 marks = repo._bookmarks
760 761 cur = repo.changectx('.').node()
761 762
762 763 if delete:
763 764 if mark is None:
764 765 raise util.Abort(_("bookmark name required"))
765 766 if mark not in marks:
766 767 raise util.Abort(_("bookmark '%s' does not exist") % mark)
767 768 if mark == repo._bookmarkcurrent:
768 769 bookmarks.setcurrent(repo, None)
769 770 del marks[mark]
770 771 bookmarks.write(repo)
771 772 return
772 773
773 774 if rename:
774 775 if rename not in marks:
775 776 raise util.Abort(_("bookmark '%s' does not exist") % rename)
776 777 if mark in marks and not force:
777 778 raise util.Abort(_("bookmark '%s' already exists "
778 779 "(use -f to force)") % mark)
779 780 if mark is None:
780 781 raise util.Abort(_("new bookmark name required"))
781 782 marks[mark] = marks[rename]
782 783 if repo._bookmarkcurrent == rename and not inactive:
783 784 bookmarks.setcurrent(repo, mark)
784 785 del marks[rename]
785 786 bookmarks.write(repo)
786 787 return
787 788
788 789 if mark is not None:
789 790 if "\n" in mark:
790 791 raise util.Abort(_("bookmark name cannot contain newlines"))
791 792 mark = mark.strip()
792 793 if not mark:
793 794 raise util.Abort(_("bookmark names cannot consist entirely of "
794 795 "whitespace"))
795 796 if inactive and mark == repo._bookmarkcurrent:
796 797 bookmarks.setcurrent(repo, None)
797 798 return
798 799 if mark in marks and not force:
799 800 raise util.Abort(_("bookmark '%s' already exists "
800 801 "(use -f to force)") % mark)
801 802 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
802 803 and not force):
803 804 raise util.Abort(
804 805 _("a bookmark cannot have the name of an existing branch"))
805 806 if rev:
806 807 marks[mark] = repo.lookup(rev)
807 808 else:
808 809 marks[mark] = cur
809 810 if not inactive and cur == marks[mark]:
810 811 bookmarks.setcurrent(repo, mark)
811 812 bookmarks.write(repo)
812 813 return
813 814
814 815 if mark is None:
815 816 if rev:
816 817 raise util.Abort(_("bookmark name required"))
817 818 if len(marks) == 0:
818 819 ui.status(_("no bookmarks set\n"))
819 820 else:
820 821 for bmark, n in sorted(marks.iteritems()):
821 822 current = repo._bookmarkcurrent
822 823 if bmark == current and n == cur:
823 824 prefix, label = '*', 'bookmarks.current'
824 825 else:
825 826 prefix, label = ' ', ''
826 827
827 828 if ui.quiet:
828 829 ui.write("%s\n" % bmark, label=label)
829 830 else:
830 831 ui.write(" %s %-25s %d:%s\n" % (
831 832 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
832 833 label=label)
833 834 return
834 835
835 836 @command('branch',
836 837 [('f', 'force', None,
837 838 _('set branch name even if it shadows an existing branch')),
838 839 ('C', 'clean', None, _('reset branch name to parent branch name'))],
839 840 _('[-fC] [NAME]'))
840 841 def branch(ui, repo, label=None, **opts):
841 842 """set or show the current branch name
842 843
843 844 .. note::
844 845 Branch names are permanent and global. Use :hg:`bookmark` to create a
845 846 light-weight bookmark instead. See :hg:`help glossary` for more
846 847 information about named branches and bookmarks.
847 848
848 849 With no argument, show the current branch name. With one argument,
849 850 set the working directory branch name (the branch will not exist
850 851 in the repository until the next commit). Standard practice
851 852 recommends that primary development take place on the 'default'
852 853 branch.
853 854
854 855 Unless -f/--force is specified, branch will not let you set a
855 856 branch name that already exists, even if it's inactive.
856 857
857 858 Use -C/--clean to reset the working directory branch to that of
858 859 the parent of the working directory, negating a previous branch
859 860 change.
860 861
861 862 Use the command :hg:`update` to switch to an existing branch. Use
862 863 :hg:`commit --close-branch` to mark this branch as closed.
863 864
864 865 Returns 0 on success.
865 866 """
866 867 if not opts.get('clean') and not label:
867 868 ui.write("%s\n" % repo.dirstate.branch())
868 869 return
869 870
870 871 wlock = repo.wlock()
871 872 try:
872 873 if opts.get('clean'):
873 874 label = repo[None].p1().branch()
874 875 repo.dirstate.setbranch(label)
875 876 ui.status(_('reset working directory to branch %s\n') % label)
876 877 elif label:
877 878 if not opts.get('force') and label in repo.branchtags():
878 879 if label not in [p.branch() for p in repo.parents()]:
879 880 raise util.Abort(_('a branch of the same name already'
880 881 ' exists'),
881 882 # i18n: "it" refers to an existing branch
882 883 hint=_("use 'hg update' to switch to it"))
883 884 repo.dirstate.setbranch(label)
884 885 ui.status(_('marked working directory as branch %s\n') % label)
885 886 ui.status(_('(branches are permanent and global, '
886 887 'did you want a bookmark?)\n'))
887 888 finally:
888 889 wlock.release()
889 890
890 891 @command('branches',
891 892 [('a', 'active', False, _('show only branches that have unmerged heads')),
892 893 ('c', 'closed', False, _('show normal and closed branches'))],
893 894 _('[-ac]'))
894 895 def branches(ui, repo, active=False, closed=False):
895 896 """list repository named branches
896 897
897 898 List the repository's named branches, indicating which ones are
898 899 inactive. If -c/--closed is specified, also list branches which have
899 900 been marked closed (see :hg:`commit --close-branch`).
900 901
901 902 If -a/--active is specified, only show active branches. A branch
902 903 is considered active if it contains repository heads.
903 904
904 905 Use the command :hg:`update` to switch to an existing branch.
905 906
906 907 Returns 0.
907 908 """
908 909
909 910 hexfunc = ui.debugflag and hex or short
910 911 activebranches = [repo[n].branch() for n in repo.heads()]
911 912 def testactive(tag, node):
912 913 realhead = tag in activebranches
913 914 open = node in repo.branchheads(tag, closed=False)
914 915 return realhead and open
915 916 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
916 917 for tag, node in repo.branchtags().items()],
917 918 reverse=True)
918 919
919 920 for isactive, node, tag in branches:
920 921 if (not active) or isactive:
921 922 if ui.quiet:
922 923 ui.write("%s\n" % tag)
923 924 else:
924 925 hn = repo.lookup(node)
925 926 if isactive:
926 927 label = 'branches.active'
927 928 notice = ''
928 929 elif hn not in repo.branchheads(tag, closed=False):
929 930 if not closed:
930 931 continue
931 932 label = 'branches.closed'
932 933 notice = _(' (closed)')
933 934 else:
934 935 label = 'branches.inactive'
935 936 notice = _(' (inactive)')
936 937 if tag == repo.dirstate.branch():
937 938 label = 'branches.current'
938 939 rev = str(node).rjust(31 - encoding.colwidth(tag))
939 940 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
940 941 tag = ui.label(tag, label)
941 942 ui.write("%s %s%s\n" % (tag, rev, notice))
942 943
943 944 @command('bundle',
944 945 [('f', 'force', None, _('run even when the destination is unrelated')),
945 946 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
946 947 _('REV')),
947 948 ('b', 'branch', [], _('a specific branch you would like to bundle'),
948 949 _('BRANCH')),
949 950 ('', 'base', [],
950 951 _('a base changeset assumed to be available at the destination'),
951 952 _('REV')),
952 953 ('a', 'all', None, _('bundle all changesets in the repository')),
953 954 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
954 955 ] + remoteopts,
955 956 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
956 957 def bundle(ui, repo, fname, dest=None, **opts):
957 958 """create a changegroup file
958 959
959 960 Generate a compressed changegroup file collecting changesets not
960 961 known to be in another repository.
961 962
962 963 If you omit the destination repository, then hg assumes the
963 964 destination will have all the nodes you specify with --base
964 965 parameters. To create a bundle containing all changesets, use
965 966 -a/--all (or --base null).
966 967
967 968 You can change compression method with the -t/--type option.
968 969 The available compression methods are: none, bzip2, and
969 970 gzip (by default, bundles are compressed using bzip2).
970 971
971 972 The bundle file can then be transferred using conventional means
972 973 and applied to another repository with the unbundle or pull
973 974 command. This is useful when direct push and pull are not
974 975 available or when exporting an entire repository is undesirable.
975 976
976 977 Applying bundles preserves all changeset contents including
977 978 permissions, copy/rename information, and revision history.
978 979
979 980 Returns 0 on success, 1 if no changes found.
980 981 """
981 982 revs = None
982 983 if 'rev' in opts:
983 984 revs = scmutil.revrange(repo, opts['rev'])
984 985
985 986 bundletype = opts.get('type', 'bzip2').lower()
986 987 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
987 988 bundletype = btypes.get(bundletype)
988 989 if bundletype not in changegroup.bundletypes:
989 990 raise util.Abort(_('unknown bundle type specified with --type'))
990 991
991 992 if opts.get('all'):
992 993 base = ['null']
993 994 else:
994 995 base = scmutil.revrange(repo, opts.get('base'))
995 996 if base:
996 997 if dest:
997 998 raise util.Abort(_("--base is incompatible with specifying "
998 999 "a destination"))
999 1000 common = [repo.lookup(rev) for rev in base]
1000 1001 heads = revs and map(repo.lookup, revs) or revs
1001 1002 cg = repo.getbundle('bundle', heads=heads, common=common)
1002 1003 outgoing = None
1003 1004 else:
1004 1005 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1005 1006 dest, branches = hg.parseurl(dest, opts.get('branch'))
1006 1007 other = hg.peer(repo, opts, dest)
1007 1008 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1008 1009 heads = revs and map(repo.lookup, revs) or revs
1009 1010 outgoing = discovery.findcommonoutgoing(repo, other,
1010 1011 onlyheads=heads,
1011 1012 force=opts.get('force'))
1012 1013 cg = repo.getlocalbundle('bundle', outgoing)
1013 1014 if not cg:
1014 1015 scmutil.nochangesfound(ui, outgoing and outgoing.excluded)
1015 1016 return 1
1016 1017
1017 1018 changegroup.writebundle(cg, fname, bundletype)
1018 1019
1019 1020 @command('cat',
1020 1021 [('o', 'output', '',
1021 1022 _('print output to file with formatted name'), _('FORMAT')),
1022 1023 ('r', 'rev', '', _('print the given revision'), _('REV')),
1023 1024 ('', 'decode', None, _('apply any matching decode filter')),
1024 1025 ] + walkopts,
1025 1026 _('[OPTION]... FILE...'))
1026 1027 def cat(ui, repo, file1, *pats, **opts):
1027 1028 """output the current or given revision of files
1028 1029
1029 1030 Print the specified files as they were at the given revision. If
1030 1031 no revision is given, the parent of the working directory is used,
1031 1032 or tip if no revision is checked out.
1032 1033
1033 1034 Output may be to a file, in which case the name of the file is
1034 1035 given using a format string. The formatting rules are the same as
1035 1036 for the export command, with the following additions:
1036 1037
1037 1038 :``%s``: basename of file being printed
1038 1039 :``%d``: dirname of file being printed, or '.' if in repository root
1039 1040 :``%p``: root-relative path name of file being printed
1040 1041
1041 1042 Returns 0 on success.
1042 1043 """
1043 1044 ctx = scmutil.revsingle(repo, opts.get('rev'))
1044 1045 err = 1
1045 1046 m = scmutil.match(ctx, (file1,) + pats, opts)
1046 1047 for abs in ctx.walk(m):
1047 1048 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1048 1049 pathname=abs)
1049 1050 data = ctx[abs].data()
1050 1051 if opts.get('decode'):
1051 1052 data = repo.wwritedata(abs, data)
1052 1053 fp.write(data)
1053 1054 fp.close()
1054 1055 err = 0
1055 1056 return err
1056 1057
1057 1058 @command('^clone',
1058 1059 [('U', 'noupdate', None,
1059 1060 _('the clone will include an empty working copy (only a repository)')),
1060 1061 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1061 1062 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1062 1063 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1063 1064 ('', 'pull', None, _('use pull protocol to copy metadata')),
1064 1065 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1065 1066 ] + remoteopts,
1066 1067 _('[OPTION]... SOURCE [DEST]'))
1067 1068 def clone(ui, source, dest=None, **opts):
1068 1069 """make a copy of an existing repository
1069 1070
1070 1071 Create a copy of an existing repository in a new directory.
1071 1072
1072 1073 If no destination directory name is specified, it defaults to the
1073 1074 basename of the source.
1074 1075
1075 1076 The location of the source is added to the new repository's
1076 1077 ``.hg/hgrc`` file, as the default to be used for future pulls.
1077 1078
1078 1079 Only local paths and ``ssh://`` URLs are supported as
1079 1080 destinations. For ``ssh://`` destinations, no working directory or
1080 1081 ``.hg/hgrc`` will be created on the remote side.
1081 1082
1082 1083 To pull only a subset of changesets, specify one or more revisions
1083 1084 identifiers with -r/--rev or branches with -b/--branch. The
1084 1085 resulting clone will contain only the specified changesets and
1085 1086 their ancestors. These options (or 'clone src#rev dest') imply
1086 1087 --pull, even for local source repositories. Note that specifying a
1087 1088 tag will include the tagged changeset but not the changeset
1088 1089 containing the tag.
1089 1090
1090 1091 To check out a particular version, use -u/--update, or
1091 1092 -U/--noupdate to create a clone with no working directory.
1092 1093
1093 1094 .. container:: verbose
1094 1095
1095 1096 For efficiency, hardlinks are used for cloning whenever the
1096 1097 source and destination are on the same filesystem (note this
1097 1098 applies only to the repository data, not to the working
1098 1099 directory). Some filesystems, such as AFS, implement hardlinking
1099 1100 incorrectly, but do not report errors. In these cases, use the
1100 1101 --pull option to avoid hardlinking.
1101 1102
1102 1103 In some cases, you can clone repositories and the working
1103 1104 directory using full hardlinks with ::
1104 1105
1105 1106 $ cp -al REPO REPOCLONE
1106 1107
1107 1108 This is the fastest way to clone, but it is not always safe. The
1108 1109 operation is not atomic (making sure REPO is not modified during
1109 1110 the operation is up to you) and you have to make sure your
1110 1111 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1111 1112 so). Also, this is not compatible with certain extensions that
1112 1113 place their metadata under the .hg directory, such as mq.
1113 1114
1114 1115 Mercurial will update the working directory to the first applicable
1115 1116 revision from this list:
1116 1117
1117 1118 a) null if -U or the source repository has no changesets
1118 1119 b) if -u . and the source repository is local, the first parent of
1119 1120 the source repository's working directory
1120 1121 c) the changeset specified with -u (if a branch name, this means the
1121 1122 latest head of that branch)
1122 1123 d) the changeset specified with -r
1123 1124 e) the tipmost head specified with -b
1124 1125 f) the tipmost head specified with the url#branch source syntax
1125 1126 g) the tipmost head of the default branch
1126 1127 h) tip
1127 1128
1128 1129 Examples:
1129 1130
1130 1131 - clone a remote repository to a new directory named hg/::
1131 1132
1132 1133 hg clone http://selenic.com/hg
1133 1134
1134 1135 - create a lightweight local clone::
1135 1136
1136 1137 hg clone project/ project-feature/
1137 1138
1138 1139 - clone from an absolute path on an ssh server (note double-slash)::
1139 1140
1140 1141 hg clone ssh://user@server//home/projects/alpha/
1141 1142
1142 1143 - do a high-speed clone over a LAN while checking out a
1143 1144 specified version::
1144 1145
1145 1146 hg clone --uncompressed http://server/repo -u 1.5
1146 1147
1147 1148 - create a repository without changesets after a particular revision::
1148 1149
1149 1150 hg clone -r 04e544 experimental/ good/
1150 1151
1151 1152 - clone (and track) a particular named branch::
1152 1153
1153 1154 hg clone http://selenic.com/hg#stable
1154 1155
1155 1156 See :hg:`help urls` for details on specifying URLs.
1156 1157
1157 1158 Returns 0 on success.
1158 1159 """
1159 1160 if opts.get('noupdate') and opts.get('updaterev'):
1160 1161 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1161 1162
1162 1163 r = hg.clone(ui, opts, source, dest,
1163 1164 pull=opts.get('pull'),
1164 1165 stream=opts.get('uncompressed'),
1165 1166 rev=opts.get('rev'),
1166 1167 update=opts.get('updaterev') or not opts.get('noupdate'),
1167 1168 branch=opts.get('branch'))
1168 1169
1169 1170 return r is None
1170 1171
1171 1172 @command('^commit|ci',
1172 1173 [('A', 'addremove', None,
1173 1174 _('mark new/missing files as added/removed before committing')),
1174 1175 ('', 'close-branch', None,
1175 1176 _('mark a branch as closed, hiding it from the branch list')),
1176 1177 ('', 'amend', None, _('amend the parent of the working dir')),
1177 1178 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1178 1179 _('[OPTION]... [FILE]...'))
1179 1180 def commit(ui, repo, *pats, **opts):
1180 1181 """commit the specified files or all outstanding changes
1181 1182
1182 1183 Commit changes to the given files into the repository. Unlike a
1183 1184 centralized SCM, this operation is a local operation. See
1184 1185 :hg:`push` for a way to actively distribute your changes.
1185 1186
1186 1187 If a list of files is omitted, all changes reported by :hg:`status`
1187 1188 will be committed.
1188 1189
1189 1190 If you are committing the result of a merge, do not provide any
1190 1191 filenames or -I/-X filters.
1191 1192
1192 1193 If no commit message is specified, Mercurial starts your
1193 1194 configured editor where you can enter a message. In case your
1194 1195 commit fails, you will find a backup of your message in
1195 1196 ``.hg/last-message.txt``.
1196 1197
1197 1198 The --amend flag can be used to amend the parent of the
1198 1199 working directory with a new commit that contains the changes
1199 1200 in the parent in addition to those currently reported by :hg:`status`,
1200 1201 if there are any. The old commit is stored in a backup bundle in
1201 1202 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1202 1203 on how to restore it).
1203 1204
1204 1205 Message, user and date are taken from the amended commit unless
1205 1206 specified. When a message isn't specified on the command line,
1206 1207 the editor will open with the message of the amended commit.
1207 1208
1208 1209 It is not possible to amend public changesets (see :hg:`help phases`)
1209 1210 or changesets that have children.
1210 1211
1211 1212 See :hg:`help dates` for a list of formats valid for -d/--date.
1212 1213
1213 1214 Returns 0 on success, 1 if nothing changed.
1214 1215 """
1215 1216 if opts.get('subrepos'):
1216 1217 # Let --subrepos on the command line overide config setting.
1217 1218 ui.setconfig('ui', 'commitsubrepos', True)
1218 1219
1219 1220 extra = {}
1220 1221 if opts.get('close_branch'):
1221 1222 if repo['.'].node() not in repo.branchheads():
1222 1223 # The topo heads set is included in the branch heads set of the
1223 1224 # current branch, so it's sufficient to test branchheads
1224 1225 raise util.Abort(_('can only close branch heads'))
1225 1226 extra['close'] = 1
1226 1227
1227 1228 branch = repo[None].branch()
1228 1229 bheads = repo.branchheads(branch)
1229 1230
1230 1231 if opts.get('amend'):
1231 1232 if ui.configbool('ui', 'commitsubrepos'):
1232 1233 raise util.Abort(_('cannot amend recursively'))
1233 1234
1234 1235 old = repo['.']
1235 1236 if old.phase() == phases.public:
1236 1237 raise util.Abort(_('cannot amend public changesets'))
1237 1238 if len(old.parents()) > 1:
1238 1239 raise util.Abort(_('cannot amend merge changesets'))
1239 1240 if len(repo[None].parents()) > 1:
1240 1241 raise util.Abort(_('cannot amend while merging'))
1241 1242 if old.children():
1242 1243 raise util.Abort(_('cannot amend changeset with children'))
1243 1244
1244 1245 e = cmdutil.commiteditor
1245 1246 if opts.get('force_editor'):
1246 1247 e = cmdutil.commitforceeditor
1247 1248
1248 1249 def commitfunc(ui, repo, message, match, opts):
1249 1250 editor = e
1250 1251 # message contains text from -m or -l, if it's empty,
1251 1252 # open the editor with the old message
1252 1253 if not message:
1253 1254 message = old.description()
1254 1255 editor = cmdutil.commitforceeditor
1255 1256 return repo.commit(message,
1256 1257 opts.get('user') or old.user(),
1257 1258 opts.get('date') or old.date(),
1258 1259 match,
1259 1260 editor=editor,
1260 1261 extra=extra)
1261 1262
1262 1263 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1263 1264 if node == old.node():
1264 1265 ui.status(_("nothing changed\n"))
1265 1266 return 1
1266 1267 else:
1267 1268 e = cmdutil.commiteditor
1268 1269 if opts.get('force_editor'):
1269 1270 e = cmdutil.commitforceeditor
1270 1271
1271 1272 def commitfunc(ui, repo, message, match, opts):
1272 1273 return repo.commit(message, opts.get('user'), opts.get('date'),
1273 1274 match, editor=e, extra=extra)
1274 1275
1275 1276 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1276 1277
1277 1278 if not node:
1278 1279 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1279 1280 if stat[3]:
1280 1281 ui.status(_("nothing changed (%d missing files, see "
1281 1282 "'hg status')\n") % len(stat[3]))
1282 1283 else:
1283 1284 ui.status(_("nothing changed\n"))
1284 1285 return 1
1285 1286
1286 1287 ctx = repo[node]
1287 1288 parents = ctx.parents()
1288 1289
1289 1290 if (not opts.get('amend') and bheads and node not in bheads and not
1290 1291 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1291 1292 ui.status(_('created new head\n'))
1292 1293 # The message is not printed for initial roots. For the other
1293 1294 # changesets, it is printed in the following situations:
1294 1295 #
1295 1296 # Par column: for the 2 parents with ...
1296 1297 # N: null or no parent
1297 1298 # B: parent is on another named branch
1298 1299 # C: parent is a regular non head changeset
1299 1300 # H: parent was a branch head of the current branch
1300 1301 # Msg column: whether we print "created new head" message
1301 1302 # In the following, it is assumed that there already exists some
1302 1303 # initial branch heads of the current branch, otherwise nothing is
1303 1304 # printed anyway.
1304 1305 #
1305 1306 # Par Msg Comment
1306 1307 # NN y additional topo root
1307 1308 #
1308 1309 # BN y additional branch root
1309 1310 # CN y additional topo head
1310 1311 # HN n usual case
1311 1312 #
1312 1313 # BB y weird additional branch root
1313 1314 # CB y branch merge
1314 1315 # HB n merge with named branch
1315 1316 #
1316 1317 # CC y additional head from merge
1317 1318 # CH n merge with a head
1318 1319 #
1319 1320 # HH n head merge: head count decreases
1320 1321
1321 1322 if not opts.get('close_branch'):
1322 1323 for r in parents:
1323 1324 if r.extra().get('close') and r.branch() == branch:
1324 1325 ui.status(_('reopening closed branch head %d\n') % r)
1325 1326
1326 1327 if ui.debugflag:
1327 1328 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1328 1329 elif ui.verbose:
1329 1330 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1330 1331
1331 1332 @command('copy|cp',
1332 1333 [('A', 'after', None, _('record a copy that has already occurred')),
1333 1334 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1334 1335 ] + walkopts + dryrunopts,
1335 1336 _('[OPTION]... [SOURCE]... DEST'))
1336 1337 def copy(ui, repo, *pats, **opts):
1337 1338 """mark files as copied for the next commit
1338 1339
1339 1340 Mark dest as having copies of source files. If dest is a
1340 1341 directory, copies are put in that directory. If dest is a file,
1341 1342 the source must be a single file.
1342 1343
1343 1344 By default, this command copies the contents of files as they
1344 1345 exist in the working directory. If invoked with -A/--after, the
1345 1346 operation is recorded, but no copying is performed.
1346 1347
1347 1348 This command takes effect with the next commit. To undo a copy
1348 1349 before that, see :hg:`revert`.
1349 1350
1350 1351 Returns 0 on success, 1 if errors are encountered.
1351 1352 """
1352 1353 wlock = repo.wlock(False)
1353 1354 try:
1354 1355 return cmdutil.copy(ui, repo, pats, opts)
1355 1356 finally:
1356 1357 wlock.release()
1357 1358
1358 1359 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1359 1360 def debugancestor(ui, repo, *args):
1360 1361 """find the ancestor revision of two revisions in a given index"""
1361 1362 if len(args) == 3:
1362 1363 index, rev1, rev2 = args
1363 1364 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1364 1365 lookup = r.lookup
1365 1366 elif len(args) == 2:
1366 1367 if not repo:
1367 1368 raise util.Abort(_("there is no Mercurial repository here "
1368 1369 "(.hg not found)"))
1369 1370 rev1, rev2 = args
1370 1371 r = repo.changelog
1371 1372 lookup = repo.lookup
1372 1373 else:
1373 1374 raise util.Abort(_('either two or three arguments required'))
1374 1375 a = r.ancestor(lookup(rev1), lookup(rev2))
1375 1376 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1376 1377
1377 1378 @command('debugbuilddag',
1378 1379 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1379 1380 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1380 1381 ('n', 'new-file', None, _('add new file at each rev'))],
1381 1382 _('[OPTION]... [TEXT]'))
1382 1383 def debugbuilddag(ui, repo, text=None,
1383 1384 mergeable_file=False,
1384 1385 overwritten_file=False,
1385 1386 new_file=False):
1386 1387 """builds a repo with a given DAG from scratch in the current empty repo
1387 1388
1388 1389 The description of the DAG is read from stdin if not given on the
1389 1390 command line.
1390 1391
1391 1392 Elements:
1392 1393
1393 1394 - "+n" is a linear run of n nodes based on the current default parent
1394 1395 - "." is a single node based on the current default parent
1395 1396 - "$" resets the default parent to null (implied at the start);
1396 1397 otherwise the default parent is always the last node created
1397 1398 - "<p" sets the default parent to the backref p
1398 1399 - "*p" is a fork at parent p, which is a backref
1399 1400 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1400 1401 - "/p2" is a merge of the preceding node and p2
1401 1402 - ":tag" defines a local tag for the preceding node
1402 1403 - "@branch" sets the named branch for subsequent nodes
1403 1404 - "#...\\n" is a comment up to the end of the line
1404 1405
1405 1406 Whitespace between the above elements is ignored.
1406 1407
1407 1408 A backref is either
1408 1409
1409 1410 - a number n, which references the node curr-n, where curr is the current
1410 1411 node, or
1411 1412 - the name of a local tag you placed earlier using ":tag", or
1412 1413 - empty to denote the default parent.
1413 1414
1414 1415 All string valued-elements are either strictly alphanumeric, or must
1415 1416 be enclosed in double quotes ("..."), with "\\" as escape character.
1416 1417 """
1417 1418
1418 1419 if text is None:
1419 1420 ui.status(_("reading DAG from stdin\n"))
1420 1421 text = ui.fin.read()
1421 1422
1422 1423 cl = repo.changelog
1423 1424 if len(cl) > 0:
1424 1425 raise util.Abort(_('repository is not empty'))
1425 1426
1426 1427 # determine number of revs in DAG
1427 1428 total = 0
1428 1429 for type, data in dagparser.parsedag(text):
1429 1430 if type == 'n':
1430 1431 total += 1
1431 1432
1432 1433 if mergeable_file:
1433 1434 linesperrev = 2
1434 1435 # make a file with k lines per rev
1435 1436 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1436 1437 initialmergedlines.append("")
1437 1438
1438 1439 tags = []
1439 1440
1440 1441 lock = tr = None
1441 1442 try:
1442 1443 lock = repo.lock()
1443 1444 tr = repo.transaction("builddag")
1444 1445
1445 1446 at = -1
1446 1447 atbranch = 'default'
1447 1448 nodeids = []
1448 1449 id = 0
1449 1450 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1450 1451 for type, data in dagparser.parsedag(text):
1451 1452 if type == 'n':
1452 1453 ui.note('node %s\n' % str(data))
1453 1454 id, ps = data
1454 1455
1455 1456 files = []
1456 1457 fctxs = {}
1457 1458
1458 1459 p2 = None
1459 1460 if mergeable_file:
1460 1461 fn = "mf"
1461 1462 p1 = repo[ps[0]]
1462 1463 if len(ps) > 1:
1463 1464 p2 = repo[ps[1]]
1464 1465 pa = p1.ancestor(p2)
1465 1466 base, local, other = [x[fn].data() for x in pa, p1, p2]
1466 1467 m3 = simplemerge.Merge3Text(base, local, other)
1467 1468 ml = [l.strip() for l in m3.merge_lines()]
1468 1469 ml.append("")
1469 1470 elif at > 0:
1470 1471 ml = p1[fn].data().split("\n")
1471 1472 else:
1472 1473 ml = initialmergedlines
1473 1474 ml[id * linesperrev] += " r%i" % id
1474 1475 mergedtext = "\n".join(ml)
1475 1476 files.append(fn)
1476 1477 fctxs[fn] = context.memfilectx(fn, mergedtext)
1477 1478
1478 1479 if overwritten_file:
1479 1480 fn = "of"
1480 1481 files.append(fn)
1481 1482 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1482 1483
1483 1484 if new_file:
1484 1485 fn = "nf%i" % id
1485 1486 files.append(fn)
1486 1487 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1487 1488 if len(ps) > 1:
1488 1489 if not p2:
1489 1490 p2 = repo[ps[1]]
1490 1491 for fn in p2:
1491 1492 if fn.startswith("nf"):
1492 1493 files.append(fn)
1493 1494 fctxs[fn] = p2[fn]
1494 1495
1495 1496 def fctxfn(repo, cx, path):
1496 1497 return fctxs.get(path)
1497 1498
1498 1499 if len(ps) == 0 or ps[0] < 0:
1499 1500 pars = [None, None]
1500 1501 elif len(ps) == 1:
1501 1502 pars = [nodeids[ps[0]], None]
1502 1503 else:
1503 1504 pars = [nodeids[p] for p in ps]
1504 1505 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1505 1506 date=(id, 0),
1506 1507 user="debugbuilddag",
1507 1508 extra={'branch': atbranch})
1508 1509 nodeid = repo.commitctx(cx)
1509 1510 nodeids.append(nodeid)
1510 1511 at = id
1511 1512 elif type == 'l':
1512 1513 id, name = data
1513 1514 ui.note('tag %s\n' % name)
1514 1515 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1515 1516 elif type == 'a':
1516 1517 ui.note('branch %s\n' % data)
1517 1518 atbranch = data
1518 1519 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1519 1520 tr.close()
1520 1521
1521 1522 if tags:
1522 1523 repo.opener.write("localtags", "".join(tags))
1523 1524 finally:
1524 1525 ui.progress(_('building'), None)
1525 1526 release(tr, lock)
1526 1527
1527 1528 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1528 1529 def debugbundle(ui, bundlepath, all=None, **opts):
1529 1530 """lists the contents of a bundle"""
1530 1531 f = url.open(ui, bundlepath)
1531 1532 try:
1532 1533 gen = changegroup.readbundle(f, bundlepath)
1533 1534 if all:
1534 1535 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1535 1536
1536 1537 def showchunks(named):
1537 1538 ui.write("\n%s\n" % named)
1538 1539 chain = None
1539 1540 while True:
1540 1541 chunkdata = gen.deltachunk(chain)
1541 1542 if not chunkdata:
1542 1543 break
1543 1544 node = chunkdata['node']
1544 1545 p1 = chunkdata['p1']
1545 1546 p2 = chunkdata['p2']
1546 1547 cs = chunkdata['cs']
1547 1548 deltabase = chunkdata['deltabase']
1548 1549 delta = chunkdata['delta']
1549 1550 ui.write("%s %s %s %s %s %s\n" %
1550 1551 (hex(node), hex(p1), hex(p2),
1551 1552 hex(cs), hex(deltabase), len(delta)))
1552 1553 chain = node
1553 1554
1554 1555 chunkdata = gen.changelogheader()
1555 1556 showchunks("changelog")
1556 1557 chunkdata = gen.manifestheader()
1557 1558 showchunks("manifest")
1558 1559 while True:
1559 1560 chunkdata = gen.filelogheader()
1560 1561 if not chunkdata:
1561 1562 break
1562 1563 fname = chunkdata['filename']
1563 1564 showchunks(fname)
1564 1565 else:
1565 1566 chunkdata = gen.changelogheader()
1566 1567 chain = None
1567 1568 while True:
1568 1569 chunkdata = gen.deltachunk(chain)
1569 1570 if not chunkdata:
1570 1571 break
1571 1572 node = chunkdata['node']
1572 1573 ui.write("%s\n" % hex(node))
1573 1574 chain = node
1574 1575 finally:
1575 1576 f.close()
1576 1577
1577 1578 @command('debugcheckstate', [], '')
1578 1579 def debugcheckstate(ui, repo):
1579 1580 """validate the correctness of the current dirstate"""
1580 1581 parent1, parent2 = repo.dirstate.parents()
1581 1582 m1 = repo[parent1].manifest()
1582 1583 m2 = repo[parent2].manifest()
1583 1584 errors = 0
1584 1585 for f in repo.dirstate:
1585 1586 state = repo.dirstate[f]
1586 1587 if state in "nr" and f not in m1:
1587 1588 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1588 1589 errors += 1
1589 1590 if state in "a" and f in m1:
1590 1591 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1591 1592 errors += 1
1592 1593 if state in "m" and f not in m1 and f not in m2:
1593 1594 ui.warn(_("%s in state %s, but not in either manifest\n") %
1594 1595 (f, state))
1595 1596 errors += 1
1596 1597 for f in m1:
1597 1598 state = repo.dirstate[f]
1598 1599 if state not in "nrm":
1599 1600 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1600 1601 errors += 1
1601 1602 if errors:
1602 1603 error = _(".hg/dirstate inconsistent with current parent's manifest")
1603 1604 raise util.Abort(error)
1604 1605
1605 1606 @command('debugcommands', [], _('[COMMAND]'))
1606 1607 def debugcommands(ui, cmd='', *args):
1607 1608 """list all available commands and options"""
1608 1609 for cmd, vals in sorted(table.iteritems()):
1609 1610 cmd = cmd.split('|')[0].strip('^')
1610 1611 opts = ', '.join([i[1] for i in vals[1]])
1611 1612 ui.write('%s: %s\n' % (cmd, opts))
1612 1613
1613 1614 @command('debugcomplete',
1614 1615 [('o', 'options', None, _('show the command options'))],
1615 1616 _('[-o] CMD'))
1616 1617 def debugcomplete(ui, cmd='', **opts):
1617 1618 """returns the completion list associated with the given command"""
1618 1619
1619 1620 if opts.get('options'):
1620 1621 options = []
1621 1622 otables = [globalopts]
1622 1623 if cmd:
1623 1624 aliases, entry = cmdutil.findcmd(cmd, table, False)
1624 1625 otables.append(entry[1])
1625 1626 for t in otables:
1626 1627 for o in t:
1627 1628 if "(DEPRECATED)" in o[3]:
1628 1629 continue
1629 1630 if o[0]:
1630 1631 options.append('-%s' % o[0])
1631 1632 options.append('--%s' % o[1])
1632 1633 ui.write("%s\n" % "\n".join(options))
1633 1634 return
1634 1635
1635 1636 cmdlist = cmdutil.findpossible(cmd, table)
1636 1637 if ui.verbose:
1637 1638 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1638 1639 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1639 1640
1640 1641 @command('debugdag',
1641 1642 [('t', 'tags', None, _('use tags as labels')),
1642 1643 ('b', 'branches', None, _('annotate with branch names')),
1643 1644 ('', 'dots', None, _('use dots for runs')),
1644 1645 ('s', 'spaces', None, _('separate elements by spaces'))],
1645 1646 _('[OPTION]... [FILE [REV]...]'))
1646 1647 def debugdag(ui, repo, file_=None, *revs, **opts):
1647 1648 """format the changelog or an index DAG as a concise textual description
1648 1649
1649 1650 If you pass a revlog index, the revlog's DAG is emitted. If you list
1650 1651 revision numbers, they get labelled in the output as rN.
1651 1652
1652 1653 Otherwise, the changelog DAG of the current repo is emitted.
1653 1654 """
1654 1655 spaces = opts.get('spaces')
1655 1656 dots = opts.get('dots')
1656 1657 if file_:
1657 1658 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1658 1659 revs = set((int(r) for r in revs))
1659 1660 def events():
1660 1661 for r in rlog:
1661 1662 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1662 1663 if r in revs:
1663 1664 yield 'l', (r, "r%i" % r)
1664 1665 elif repo:
1665 1666 cl = repo.changelog
1666 1667 tags = opts.get('tags')
1667 1668 branches = opts.get('branches')
1668 1669 if tags:
1669 1670 labels = {}
1670 1671 for l, n in repo.tags().items():
1671 1672 labels.setdefault(cl.rev(n), []).append(l)
1672 1673 def events():
1673 1674 b = "default"
1674 1675 for r in cl:
1675 1676 if branches:
1676 1677 newb = cl.read(cl.node(r))[5]['branch']
1677 1678 if newb != b:
1678 1679 yield 'a', newb
1679 1680 b = newb
1680 1681 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1681 1682 if tags:
1682 1683 ls = labels.get(r)
1683 1684 if ls:
1684 1685 for l in ls:
1685 1686 yield 'l', (r, l)
1686 1687 else:
1687 1688 raise util.Abort(_('need repo for changelog dag'))
1688 1689
1689 1690 for line in dagparser.dagtextlines(events(),
1690 1691 addspaces=spaces,
1691 1692 wraplabels=True,
1692 1693 wrapannotations=True,
1693 1694 wrapnonlinear=dots,
1694 1695 usedots=dots,
1695 1696 maxlinewidth=70):
1696 1697 ui.write(line)
1697 1698 ui.write("\n")
1698 1699
1699 1700 @command('debugdata',
1700 1701 [('c', 'changelog', False, _('open changelog')),
1701 1702 ('m', 'manifest', False, _('open manifest'))],
1702 1703 _('-c|-m|FILE REV'))
1703 1704 def debugdata(ui, repo, file_, rev = None, **opts):
1704 1705 """dump the contents of a data file revision"""
1705 1706 if opts.get('changelog') or opts.get('manifest'):
1706 1707 file_, rev = None, file_
1707 1708 elif rev is None:
1708 1709 raise error.CommandError('debugdata', _('invalid arguments'))
1709 1710 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1710 1711 try:
1711 1712 ui.write(r.revision(r.lookup(rev)))
1712 1713 except KeyError:
1713 1714 raise util.Abort(_('invalid revision identifier %s') % rev)
1714 1715
1715 1716 @command('debugdate',
1716 1717 [('e', 'extended', None, _('try extended date formats'))],
1717 1718 _('[-e] DATE [RANGE]'))
1718 1719 def debugdate(ui, date, range=None, **opts):
1719 1720 """parse and display a date"""
1720 1721 if opts["extended"]:
1721 1722 d = util.parsedate(date, util.extendeddateformats)
1722 1723 else:
1723 1724 d = util.parsedate(date)
1724 1725 ui.write("internal: %s %s\n" % d)
1725 1726 ui.write("standard: %s\n" % util.datestr(d))
1726 1727 if range:
1727 1728 m = util.matchdate(range)
1728 1729 ui.write("match: %s\n" % m(d[0]))
1729 1730
1730 1731 @command('debugdiscovery',
1731 1732 [('', 'old', None, _('use old-style discovery')),
1732 1733 ('', 'nonheads', None,
1733 1734 _('use old-style discovery with non-heads included')),
1734 1735 ] + remoteopts,
1735 1736 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1736 1737 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1737 1738 """runs the changeset discovery protocol in isolation"""
1738 1739 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1739 1740 remote = hg.peer(repo, opts, remoteurl)
1740 1741 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1741 1742
1742 1743 # make sure tests are repeatable
1743 1744 random.seed(12323)
1744 1745
1745 1746 def doit(localheads, remoteheads):
1746 1747 if opts.get('old'):
1747 1748 if localheads:
1748 1749 raise util.Abort('cannot use localheads with old style discovery')
1749 1750 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1750 1751 force=True)
1751 1752 common = set(common)
1752 1753 if not opts.get('nonheads'):
1753 1754 ui.write("unpruned common: %s\n" % " ".join([short(n)
1754 1755 for n in common]))
1755 1756 dag = dagutil.revlogdag(repo.changelog)
1756 1757 all = dag.ancestorset(dag.internalizeall(common))
1757 1758 common = dag.externalizeall(dag.headsetofconnecteds(all))
1758 1759 else:
1759 1760 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1760 1761 common = set(common)
1761 1762 rheads = set(hds)
1762 1763 lheads = set(repo.heads())
1763 1764 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1764 1765 if lheads <= common:
1765 1766 ui.write("local is subset\n")
1766 1767 elif rheads <= common:
1767 1768 ui.write("remote is subset\n")
1768 1769
1769 1770 serverlogs = opts.get('serverlog')
1770 1771 if serverlogs:
1771 1772 for filename in serverlogs:
1772 1773 logfile = open(filename, 'r')
1773 1774 try:
1774 1775 line = logfile.readline()
1775 1776 while line:
1776 1777 parts = line.strip().split(';')
1777 1778 op = parts[1]
1778 1779 if op == 'cg':
1779 1780 pass
1780 1781 elif op == 'cgss':
1781 1782 doit(parts[2].split(' '), parts[3].split(' '))
1782 1783 elif op == 'unb':
1783 1784 doit(parts[3].split(' '), parts[2].split(' '))
1784 1785 line = logfile.readline()
1785 1786 finally:
1786 1787 logfile.close()
1787 1788
1788 1789 else:
1789 1790 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1790 1791 opts.get('remote_head'))
1791 1792 localrevs = opts.get('local_head')
1792 1793 doit(localrevs, remoterevs)
1793 1794
1794 1795 @command('debugfileset', [], ('REVSPEC'))
1795 1796 def debugfileset(ui, repo, expr):
1796 1797 '''parse and apply a fileset specification'''
1797 1798 if ui.verbose:
1798 1799 tree = fileset.parse(expr)[0]
1799 1800 ui.note(tree, "\n")
1800 1801
1801 1802 for f in fileset.getfileset(repo[None], expr):
1802 1803 ui.write("%s\n" % f)
1803 1804
1804 1805 @command('debugfsinfo', [], _('[PATH]'))
1805 1806 def debugfsinfo(ui, path = "."):
1806 1807 """show information detected about current filesystem"""
1807 1808 util.writefile('.debugfsinfo', '')
1808 1809 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1809 1810 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1810 1811 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1811 1812 and 'yes' or 'no'))
1812 1813 os.unlink('.debugfsinfo')
1813 1814
1814 1815 @command('debuggetbundle',
1815 1816 [('H', 'head', [], _('id of head node'), _('ID')),
1816 1817 ('C', 'common', [], _('id of common node'), _('ID')),
1817 1818 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1818 1819 _('REPO FILE [-H|-C ID]...'))
1819 1820 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1820 1821 """retrieves a bundle from a repo
1821 1822
1822 1823 Every ID must be a full-length hex node id string. Saves the bundle to the
1823 1824 given file.
1824 1825 """
1825 1826 repo = hg.peer(ui, opts, repopath)
1826 1827 if not repo.capable('getbundle'):
1827 1828 raise util.Abort("getbundle() not supported by target repository")
1828 1829 args = {}
1829 1830 if common:
1830 1831 args['common'] = [bin(s) for s in common]
1831 1832 if head:
1832 1833 args['heads'] = [bin(s) for s in head]
1833 1834 bundle = repo.getbundle('debug', **args)
1834 1835
1835 1836 bundletype = opts.get('type', 'bzip2').lower()
1836 1837 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1837 1838 bundletype = btypes.get(bundletype)
1838 1839 if bundletype not in changegroup.bundletypes:
1839 1840 raise util.Abort(_('unknown bundle type specified with --type'))
1840 1841 changegroup.writebundle(bundle, bundlepath, bundletype)
1841 1842
1842 1843 @command('debugignore', [], '')
1843 1844 def debugignore(ui, repo, *values, **opts):
1844 1845 """display the combined ignore pattern"""
1845 1846 ignore = repo.dirstate._ignore
1846 1847 includepat = getattr(ignore, 'includepat', None)
1847 1848 if includepat is not None:
1848 1849 ui.write("%s\n" % includepat)
1849 1850 else:
1850 1851 raise util.Abort(_("no ignore patterns found"))
1851 1852
1852 1853 @command('debugindex',
1853 1854 [('c', 'changelog', False, _('open changelog')),
1854 1855 ('m', 'manifest', False, _('open manifest')),
1855 1856 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1856 1857 _('[-f FORMAT] -c|-m|FILE'))
1857 1858 def debugindex(ui, repo, file_ = None, **opts):
1858 1859 """dump the contents of an index file"""
1859 1860 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1860 1861 format = opts.get('format', 0)
1861 1862 if format not in (0, 1):
1862 1863 raise util.Abort(_("unknown format %d") % format)
1863 1864
1864 1865 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1865 1866 if generaldelta:
1866 1867 basehdr = ' delta'
1867 1868 else:
1868 1869 basehdr = ' base'
1869 1870
1870 1871 if format == 0:
1871 1872 ui.write(" rev offset length " + basehdr + " linkrev"
1872 1873 " nodeid p1 p2\n")
1873 1874 elif format == 1:
1874 1875 ui.write(" rev flag offset length"
1875 1876 " size " + basehdr + " link p1 p2 nodeid\n")
1876 1877
1877 1878 for i in r:
1878 1879 node = r.node(i)
1879 1880 if generaldelta:
1880 1881 base = r.deltaparent(i)
1881 1882 else:
1882 1883 base = r.chainbase(i)
1883 1884 if format == 0:
1884 1885 try:
1885 1886 pp = r.parents(node)
1886 1887 except:
1887 1888 pp = [nullid, nullid]
1888 1889 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1889 1890 i, r.start(i), r.length(i), base, r.linkrev(i),
1890 1891 short(node), short(pp[0]), short(pp[1])))
1891 1892 elif format == 1:
1892 1893 pr = r.parentrevs(i)
1893 1894 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1894 1895 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1895 1896 base, r.linkrev(i), pr[0], pr[1], short(node)))
1896 1897
1897 1898 @command('debugindexdot', [], _('FILE'))
1898 1899 def debugindexdot(ui, repo, file_):
1899 1900 """dump an index DAG as a graphviz dot file"""
1900 1901 r = None
1901 1902 if repo:
1902 1903 filelog = repo.file(file_)
1903 1904 if len(filelog):
1904 1905 r = filelog
1905 1906 if not r:
1906 1907 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1907 1908 ui.write("digraph G {\n")
1908 1909 for i in r:
1909 1910 node = r.node(i)
1910 1911 pp = r.parents(node)
1911 1912 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1912 1913 if pp[1] != nullid:
1913 1914 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1914 1915 ui.write("}\n")
1915 1916
1916 1917 @command('debuginstall', [], '')
1917 1918 def debuginstall(ui):
1918 1919 '''test Mercurial installation
1919 1920
1920 1921 Returns 0 on success.
1921 1922 '''
1922 1923
1923 1924 def writetemp(contents):
1924 1925 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1925 1926 f = os.fdopen(fd, "wb")
1926 1927 f.write(contents)
1927 1928 f.close()
1928 1929 return name
1929 1930
1930 1931 problems = 0
1931 1932
1932 1933 # encoding
1933 1934 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1934 1935 try:
1935 1936 encoding.fromlocal("test")
1936 1937 except util.Abort, inst:
1937 1938 ui.write(" %s\n" % inst)
1938 1939 ui.write(_(" (check that your locale is properly set)\n"))
1939 1940 problems += 1
1940 1941
1941 1942 # compiled modules
1942 1943 ui.status(_("Checking installed modules (%s)...\n")
1943 1944 % os.path.dirname(__file__))
1944 1945 try:
1945 1946 import bdiff, mpatch, base85, osutil
1946 1947 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1947 1948 except Exception, inst:
1948 1949 ui.write(" %s\n" % inst)
1949 1950 ui.write(_(" One or more extensions could not be found"))
1950 1951 ui.write(_(" (check that you compiled the extensions)\n"))
1951 1952 problems += 1
1952 1953
1953 1954 # templates
1954 1955 import templater
1955 1956 p = templater.templatepath()
1956 1957 ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
1957 1958 try:
1958 1959 templater.templater(templater.templatepath("map-cmdline.default"))
1959 1960 except Exception, inst:
1960 1961 ui.write(" %s\n" % inst)
1961 1962 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1962 1963 problems += 1
1963 1964
1964 1965 # editor
1965 1966 ui.status(_("Checking commit editor...\n"))
1966 1967 editor = ui.geteditor()
1967 1968 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1968 1969 if not cmdpath:
1969 1970 if editor == 'vi':
1970 1971 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1971 1972 ui.write(_(" (specify a commit editor in your configuration"
1972 1973 " file)\n"))
1973 1974 else:
1974 1975 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1975 1976 ui.write(_(" (specify a commit editor in your configuration"
1976 1977 " file)\n"))
1977 1978 problems += 1
1978 1979
1979 1980 # check username
1980 1981 ui.status(_("Checking username...\n"))
1981 1982 try:
1982 1983 ui.username()
1983 1984 except util.Abort, e:
1984 1985 ui.write(" %s\n" % e)
1985 1986 ui.write(_(" (specify a username in your configuration file)\n"))
1986 1987 problems += 1
1987 1988
1988 1989 if not problems:
1989 1990 ui.status(_("No problems detected\n"))
1990 1991 else:
1991 1992 ui.write(_("%s problems detected,"
1992 1993 " please check your install!\n") % problems)
1993 1994
1994 1995 return problems
1995 1996
1996 1997 @command('debugknown', [], _('REPO ID...'))
1997 1998 def debugknown(ui, repopath, *ids, **opts):
1998 1999 """test whether node ids are known to a repo
1999 2000
2000 2001 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
2001 2002 indicating unknown/known.
2002 2003 """
2003 2004 repo = hg.peer(ui, opts, repopath)
2004 2005 if not repo.capable('known'):
2005 2006 raise util.Abort("known() not supported by target repository")
2006 2007 flags = repo.known([bin(s) for s in ids])
2007 2008 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2008 2009
2009 2010 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2010 2011 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2011 2012 '''access the pushkey key/value protocol
2012 2013
2013 2014 With two args, list the keys in the given namespace.
2014 2015
2015 2016 With five args, set a key to new if it currently is set to old.
2016 2017 Reports success or failure.
2017 2018 '''
2018 2019
2019 2020 target = hg.peer(ui, {}, repopath)
2020 2021 if keyinfo:
2021 2022 key, old, new = keyinfo
2022 2023 r = target.pushkey(namespace, key, old, new)
2023 2024 ui.status(str(r) + '\n')
2024 2025 return not r
2025 2026 else:
2026 2027 for k, v in target.listkeys(namespace).iteritems():
2027 2028 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2028 2029 v.encode('string-escape')))
2029 2030
2030 2031 @command('debugpvec', [], _('A B'))
2031 2032 def debugpvec(ui, repo, a, b=None):
2032 2033 ca = scmutil.revsingle(repo, a)
2033 2034 cb = scmutil.revsingle(repo, b)
2034 2035 pa = pvec.ctxpvec(ca)
2035 2036 pb = pvec.ctxpvec(cb)
2036 2037 if pa == pb:
2037 2038 rel = "="
2038 2039 elif pa > pb:
2039 2040 rel = ">"
2040 2041 elif pa < pb:
2041 2042 rel = "<"
2042 2043 elif pa | pb:
2043 2044 rel = "|"
2044 2045 ui.write(_("a: %s\n") % pa)
2045 2046 ui.write(_("b: %s\n") % pb)
2046 2047 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2047 2048 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2048 2049 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2049 2050 pa.distance(pb), rel))
2050 2051
2051 2052 @command('debugrebuildstate',
2052 2053 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2053 2054 _('[-r REV] [REV]'))
2054 2055 def debugrebuildstate(ui, repo, rev="tip"):
2055 2056 """rebuild the dirstate as it would look like for the given revision"""
2056 2057 ctx = scmutil.revsingle(repo, rev)
2057 2058 wlock = repo.wlock()
2058 2059 try:
2059 2060 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2060 2061 finally:
2061 2062 wlock.release()
2062 2063
2063 2064 @command('debugrename',
2064 2065 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2065 2066 _('[-r REV] FILE'))
2066 2067 def debugrename(ui, repo, file1, *pats, **opts):
2067 2068 """dump rename information"""
2068 2069
2069 2070 ctx = scmutil.revsingle(repo, opts.get('rev'))
2070 2071 m = scmutil.match(ctx, (file1,) + pats, opts)
2071 2072 for abs in ctx.walk(m):
2072 2073 fctx = ctx[abs]
2073 2074 o = fctx.filelog().renamed(fctx.filenode())
2074 2075 rel = m.rel(abs)
2075 2076 if o:
2076 2077 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2077 2078 else:
2078 2079 ui.write(_("%s not renamed\n") % rel)
2079 2080
2080 2081 @command('debugrevlog',
2081 2082 [('c', 'changelog', False, _('open changelog')),
2082 2083 ('m', 'manifest', False, _('open manifest')),
2083 2084 ('d', 'dump', False, _('dump index data'))],
2084 2085 _('-c|-m|FILE'))
2085 2086 def debugrevlog(ui, repo, file_ = None, **opts):
2086 2087 """show data and statistics about a revlog"""
2087 2088 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2088 2089
2089 2090 if opts.get("dump"):
2090 2091 numrevs = len(r)
2091 2092 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2092 2093 " rawsize totalsize compression heads\n")
2093 2094 ts = 0
2094 2095 heads = set()
2095 2096 for rev in xrange(numrevs):
2096 2097 dbase = r.deltaparent(rev)
2097 2098 if dbase == -1:
2098 2099 dbase = rev
2099 2100 cbase = r.chainbase(rev)
2100 2101 p1, p2 = r.parentrevs(rev)
2101 2102 rs = r.rawsize(rev)
2102 2103 ts = ts + rs
2103 2104 heads -= set(r.parentrevs(rev))
2104 2105 heads.add(rev)
2105 2106 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2106 2107 (rev, p1, p2, r.start(rev), r.end(rev),
2107 2108 r.start(dbase), r.start(cbase),
2108 2109 r.start(p1), r.start(p2),
2109 2110 rs, ts, ts / r.end(rev), len(heads)))
2110 2111 return 0
2111 2112
2112 2113 v = r.version
2113 2114 format = v & 0xFFFF
2114 2115 flags = []
2115 2116 gdelta = False
2116 2117 if v & revlog.REVLOGNGINLINEDATA:
2117 2118 flags.append('inline')
2118 2119 if v & revlog.REVLOGGENERALDELTA:
2119 2120 gdelta = True
2120 2121 flags.append('generaldelta')
2121 2122 if not flags:
2122 2123 flags = ['(none)']
2123 2124
2124 2125 nummerges = 0
2125 2126 numfull = 0
2126 2127 numprev = 0
2127 2128 nump1 = 0
2128 2129 nump2 = 0
2129 2130 numother = 0
2130 2131 nump1prev = 0
2131 2132 nump2prev = 0
2132 2133 chainlengths = []
2133 2134
2134 2135 datasize = [None, 0, 0L]
2135 2136 fullsize = [None, 0, 0L]
2136 2137 deltasize = [None, 0, 0L]
2137 2138
2138 2139 def addsize(size, l):
2139 2140 if l[0] is None or size < l[0]:
2140 2141 l[0] = size
2141 2142 if size > l[1]:
2142 2143 l[1] = size
2143 2144 l[2] += size
2144 2145
2145 2146 numrevs = len(r)
2146 2147 for rev in xrange(numrevs):
2147 2148 p1, p2 = r.parentrevs(rev)
2148 2149 delta = r.deltaparent(rev)
2149 2150 if format > 0:
2150 2151 addsize(r.rawsize(rev), datasize)
2151 2152 if p2 != nullrev:
2152 2153 nummerges += 1
2153 2154 size = r.length(rev)
2154 2155 if delta == nullrev:
2155 2156 chainlengths.append(0)
2156 2157 numfull += 1
2157 2158 addsize(size, fullsize)
2158 2159 else:
2159 2160 chainlengths.append(chainlengths[delta] + 1)
2160 2161 addsize(size, deltasize)
2161 2162 if delta == rev - 1:
2162 2163 numprev += 1
2163 2164 if delta == p1:
2164 2165 nump1prev += 1
2165 2166 elif delta == p2:
2166 2167 nump2prev += 1
2167 2168 elif delta == p1:
2168 2169 nump1 += 1
2169 2170 elif delta == p2:
2170 2171 nump2 += 1
2171 2172 elif delta != nullrev:
2172 2173 numother += 1
2173 2174
2174 2175 numdeltas = numrevs - numfull
2175 2176 numoprev = numprev - nump1prev - nump2prev
2176 2177 totalrawsize = datasize[2]
2177 2178 datasize[2] /= numrevs
2178 2179 fulltotal = fullsize[2]
2179 2180 fullsize[2] /= numfull
2180 2181 deltatotal = deltasize[2]
2181 2182 deltasize[2] /= numrevs - numfull
2182 2183 totalsize = fulltotal + deltatotal
2183 2184 avgchainlen = sum(chainlengths) / numrevs
2184 2185 compratio = totalrawsize / totalsize
2185 2186
2186 2187 basedfmtstr = '%%%dd\n'
2187 2188 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2188 2189
2189 2190 def dfmtstr(max):
2190 2191 return basedfmtstr % len(str(max))
2191 2192 def pcfmtstr(max, padding=0):
2192 2193 return basepcfmtstr % (len(str(max)), ' ' * padding)
2193 2194
2194 2195 def pcfmt(value, total):
2195 2196 return (value, 100 * float(value) / total)
2196 2197
2197 2198 ui.write('format : %d\n' % format)
2198 2199 ui.write('flags : %s\n' % ', '.join(flags))
2199 2200
2200 2201 ui.write('\n')
2201 2202 fmt = pcfmtstr(totalsize)
2202 2203 fmt2 = dfmtstr(totalsize)
2203 2204 ui.write('revisions : ' + fmt2 % numrevs)
2204 2205 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2205 2206 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2206 2207 ui.write('revisions : ' + fmt2 % numrevs)
2207 2208 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2208 2209 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2209 2210 ui.write('revision size : ' + fmt2 % totalsize)
2210 2211 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2211 2212 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2212 2213
2213 2214 ui.write('\n')
2214 2215 fmt = dfmtstr(max(avgchainlen, compratio))
2215 2216 ui.write('avg chain length : ' + fmt % avgchainlen)
2216 2217 ui.write('compression ratio : ' + fmt % compratio)
2217 2218
2218 2219 if format > 0:
2219 2220 ui.write('\n')
2220 2221 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2221 2222 % tuple(datasize))
2222 2223 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2223 2224 % tuple(fullsize))
2224 2225 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2225 2226 % tuple(deltasize))
2226 2227
2227 2228 if numdeltas > 0:
2228 2229 ui.write('\n')
2229 2230 fmt = pcfmtstr(numdeltas)
2230 2231 fmt2 = pcfmtstr(numdeltas, 4)
2231 2232 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2232 2233 if numprev > 0:
2233 2234 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2234 2235 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2235 2236 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2236 2237 if gdelta:
2237 2238 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2238 2239 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2239 2240 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2240 2241
2241 2242 @command('debugrevspec', [], ('REVSPEC'))
2242 2243 def debugrevspec(ui, repo, expr):
2243 2244 """parse and apply a revision specification
2244 2245
2245 2246 Use --verbose to print the parsed tree before and after aliases
2246 2247 expansion.
2247 2248 """
2248 2249 if ui.verbose:
2249 2250 tree = revset.parse(expr)[0]
2250 2251 ui.note(revset.prettyformat(tree), "\n")
2251 2252 newtree = revset.findaliases(ui, tree)
2252 2253 if newtree != tree:
2253 2254 ui.note(revset.prettyformat(newtree), "\n")
2254 2255 func = revset.match(ui, expr)
2255 2256 for c in func(repo, range(len(repo))):
2256 2257 ui.write("%s\n" % c)
2257 2258
2258 2259 @command('debugsetparents', [], _('REV1 [REV2]'))
2259 2260 def debugsetparents(ui, repo, rev1, rev2=None):
2260 2261 """manually set the parents of the current working directory
2261 2262
2262 2263 This is useful for writing repository conversion tools, but should
2263 2264 be used with care.
2264 2265
2265 2266 Returns 0 on success.
2266 2267 """
2267 2268
2268 2269 r1 = scmutil.revsingle(repo, rev1).node()
2269 2270 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2270 2271
2271 2272 wlock = repo.wlock()
2272 2273 try:
2273 2274 repo.setparents(r1, r2)
2274 2275 finally:
2275 2276 wlock.release()
2276 2277
2277 2278 @command('debugstate',
2278 2279 [('', 'nodates', None, _('do not display the saved mtime')),
2279 2280 ('', 'datesort', None, _('sort by saved mtime'))],
2280 2281 _('[OPTION]...'))
2281 2282 def debugstate(ui, repo, nodates=None, datesort=None):
2282 2283 """show the contents of the current dirstate"""
2283 2284 timestr = ""
2284 2285 showdate = not nodates
2285 2286 if datesort:
2286 2287 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2287 2288 else:
2288 2289 keyfunc = None # sort by filename
2289 2290 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2290 2291 if showdate:
2291 2292 if ent[3] == -1:
2292 2293 # Pad or slice to locale representation
2293 2294 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2294 2295 time.localtime(0)))
2295 2296 timestr = 'unset'
2296 2297 timestr = (timestr[:locale_len] +
2297 2298 ' ' * (locale_len - len(timestr)))
2298 2299 else:
2299 2300 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2300 2301 time.localtime(ent[3]))
2301 2302 if ent[1] & 020000:
2302 2303 mode = 'lnk'
2303 2304 else:
2304 2305 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2305 2306 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2306 2307 for f in repo.dirstate.copies():
2307 2308 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2308 2309
2309 2310 @command('debugsub',
2310 2311 [('r', 'rev', '',
2311 2312 _('revision to check'), _('REV'))],
2312 2313 _('[-r REV] [REV]'))
2313 2314 def debugsub(ui, repo, rev=None):
2314 2315 ctx = scmutil.revsingle(repo, rev, None)
2315 2316 for k, v in sorted(ctx.substate.items()):
2316 2317 ui.write('path %s\n' % k)
2317 2318 ui.write(' source %s\n' % v[0])
2318 2319 ui.write(' revision %s\n' % v[1])
2319 2320
2320 2321 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2321 2322 def debugwalk(ui, repo, *pats, **opts):
2322 2323 """show how files match on given patterns"""
2323 2324 m = scmutil.match(repo[None], pats, opts)
2324 2325 items = list(repo.walk(m))
2325 2326 if not items:
2326 2327 return
2327 2328 fmt = 'f %%-%ds %%-%ds %%s' % (
2328 2329 max([len(abs) for abs in items]),
2329 2330 max([len(m.rel(abs)) for abs in items]))
2330 2331 for abs in items:
2331 2332 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2332 2333 ui.write("%s\n" % line.rstrip())
2333 2334
2334 2335 @command('debugwireargs',
2335 2336 [('', 'three', '', 'three'),
2336 2337 ('', 'four', '', 'four'),
2337 2338 ('', 'five', '', 'five'),
2338 2339 ] + remoteopts,
2339 2340 _('REPO [OPTIONS]... [ONE [TWO]]'))
2340 2341 def debugwireargs(ui, repopath, *vals, **opts):
2341 2342 repo = hg.peer(ui, opts, repopath)
2342 2343 for opt in remoteopts:
2343 2344 del opts[opt[1]]
2344 2345 args = {}
2345 2346 for k, v in opts.iteritems():
2346 2347 if v:
2347 2348 args[k] = v
2348 2349 # run twice to check that we don't mess up the stream for the next command
2349 2350 res1 = repo.debugwireargs(*vals, **args)
2350 2351 res2 = repo.debugwireargs(*vals, **args)
2351 2352 ui.write("%s\n" % res1)
2352 2353 if res1 != res2:
2353 2354 ui.warn("%s\n" % res2)
2354 2355
2355 2356 @command('^diff',
2356 2357 [('r', 'rev', [], _('revision'), _('REV')),
2357 2358 ('c', 'change', '', _('change made by revision'), _('REV'))
2358 2359 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2359 2360 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2360 2361 def diff(ui, repo, *pats, **opts):
2361 2362 """diff repository (or selected files)
2362 2363
2363 2364 Show differences between revisions for the specified files.
2364 2365
2365 2366 Differences between files are shown using the unified diff format.
2366 2367
2367 2368 .. note::
2368 2369 diff may generate unexpected results for merges, as it will
2369 2370 default to comparing against the working directory's first
2370 2371 parent changeset if no revisions are specified.
2371 2372
2372 2373 When two revision arguments are given, then changes are shown
2373 2374 between those revisions. If only one revision is specified then
2374 2375 that revision is compared to the working directory, and, when no
2375 2376 revisions are specified, the working directory files are compared
2376 2377 to its parent.
2377 2378
2378 2379 Alternatively you can specify -c/--change with a revision to see
2379 2380 the changes in that changeset relative to its first parent.
2380 2381
2381 2382 Without the -a/--text option, diff will avoid generating diffs of
2382 2383 files it detects as binary. With -a, diff will generate a diff
2383 2384 anyway, probably with undesirable results.
2384 2385
2385 2386 Use the -g/--git option to generate diffs in the git extended diff
2386 2387 format. For more information, read :hg:`help diffs`.
2387 2388
2388 2389 .. container:: verbose
2389 2390
2390 2391 Examples:
2391 2392
2392 2393 - compare a file in the current working directory to its parent::
2393 2394
2394 2395 hg diff foo.c
2395 2396
2396 2397 - compare two historical versions of a directory, with rename info::
2397 2398
2398 2399 hg diff --git -r 1.0:1.2 lib/
2399 2400
2400 2401 - get change stats relative to the last change on some date::
2401 2402
2402 2403 hg diff --stat -r "date('may 2')"
2403 2404
2404 2405 - diff all newly-added files that contain a keyword::
2405 2406
2406 2407 hg diff "set:added() and grep(GNU)"
2407 2408
2408 2409 - compare a revision and its parents::
2409 2410
2410 2411 hg diff -c 9353 # compare against first parent
2411 2412 hg diff -r 9353^:9353 # same using revset syntax
2412 2413 hg diff -r 9353^2:9353 # compare against the second parent
2413 2414
2414 2415 Returns 0 on success.
2415 2416 """
2416 2417
2417 2418 revs = opts.get('rev')
2418 2419 change = opts.get('change')
2419 2420 stat = opts.get('stat')
2420 2421 reverse = opts.get('reverse')
2421 2422
2422 2423 if revs and change:
2423 2424 msg = _('cannot specify --rev and --change at the same time')
2424 2425 raise util.Abort(msg)
2425 2426 elif change:
2426 2427 node2 = scmutil.revsingle(repo, change, None).node()
2427 2428 node1 = repo[node2].p1().node()
2428 2429 else:
2429 2430 node1, node2 = scmutil.revpair(repo, revs)
2430 2431
2431 2432 if reverse:
2432 2433 node1, node2 = node2, node1
2433 2434
2434 2435 diffopts = patch.diffopts(ui, opts)
2435 2436 m = scmutil.match(repo[node2], pats, opts)
2436 2437 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2437 2438 listsubrepos=opts.get('subrepos'))
2438 2439
2439 2440 @command('^export',
2440 2441 [('o', 'output', '',
2441 2442 _('print output to file with formatted name'), _('FORMAT')),
2442 2443 ('', 'switch-parent', None, _('diff against the second parent')),
2443 2444 ('r', 'rev', [], _('revisions to export'), _('REV')),
2444 2445 ] + diffopts,
2445 2446 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2446 2447 def export(ui, repo, *changesets, **opts):
2447 2448 """dump the header and diffs for one or more changesets
2448 2449
2449 2450 Print the changeset header and diffs for one or more revisions.
2450 2451
2451 2452 The information shown in the changeset header is: author, date,
2452 2453 branch name (if non-default), changeset hash, parent(s) and commit
2453 2454 comment.
2454 2455
2455 2456 .. note::
2456 2457 export may generate unexpected diff output for merge
2457 2458 changesets, as it will compare the merge changeset against its
2458 2459 first parent only.
2459 2460
2460 2461 Output may be to a file, in which case the name of the file is
2461 2462 given using a format string. The formatting rules are as follows:
2462 2463
2463 2464 :``%%``: literal "%" character
2464 2465 :``%H``: changeset hash (40 hexadecimal digits)
2465 2466 :``%N``: number of patches being generated
2466 2467 :``%R``: changeset revision number
2467 2468 :``%b``: basename of the exporting repository
2468 2469 :``%h``: short-form changeset hash (12 hexadecimal digits)
2469 2470 :``%m``: first line of the commit message (only alphanumeric characters)
2470 2471 :``%n``: zero-padded sequence number, starting at 1
2471 2472 :``%r``: zero-padded changeset revision number
2472 2473
2473 2474 Without the -a/--text option, export will avoid generating diffs
2474 2475 of files it detects as binary. With -a, export will generate a
2475 2476 diff anyway, probably with undesirable results.
2476 2477
2477 2478 Use the -g/--git option to generate diffs in the git extended diff
2478 2479 format. See :hg:`help diffs` for more information.
2479 2480
2480 2481 With the --switch-parent option, the diff will be against the
2481 2482 second parent. It can be useful to review a merge.
2482 2483
2483 2484 .. container:: verbose
2484 2485
2485 2486 Examples:
2486 2487
2487 2488 - use export and import to transplant a bugfix to the current
2488 2489 branch::
2489 2490
2490 2491 hg export -r 9353 | hg import -
2491 2492
2492 2493 - export all the changesets between two revisions to a file with
2493 2494 rename information::
2494 2495
2495 2496 hg export --git -r 123:150 > changes.txt
2496 2497
2497 2498 - split outgoing changes into a series of patches with
2498 2499 descriptive names::
2499 2500
2500 2501 hg export -r "outgoing()" -o "%n-%m.patch"
2501 2502
2502 2503 Returns 0 on success.
2503 2504 """
2504 2505 changesets += tuple(opts.get('rev', []))
2505 2506 revs = scmutil.revrange(repo, changesets)
2506 2507 if not revs:
2507 2508 raise util.Abort(_("export requires at least one changeset"))
2508 2509 if len(revs) > 1:
2509 2510 ui.note(_('exporting patches:\n'))
2510 2511 else:
2511 2512 ui.note(_('exporting patch:\n'))
2512 2513 cmdutil.export(repo, revs, template=opts.get('output'),
2513 2514 switch_parent=opts.get('switch_parent'),
2514 2515 opts=patch.diffopts(ui, opts))
2515 2516
2516 2517 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2517 2518 def forget(ui, repo, *pats, **opts):
2518 2519 """forget the specified files on the next commit
2519 2520
2520 2521 Mark the specified files so they will no longer be tracked
2521 2522 after the next commit.
2522 2523
2523 2524 This only removes files from the current branch, not from the
2524 2525 entire project history, and it does not delete them from the
2525 2526 working directory.
2526 2527
2527 2528 To undo a forget before the next commit, see :hg:`add`.
2528 2529
2529 2530 .. container:: verbose
2530 2531
2531 2532 Examples:
2532 2533
2533 2534 - forget newly-added binary files::
2534 2535
2535 2536 hg forget "set:added() and binary()"
2536 2537
2537 2538 - forget files that would be excluded by .hgignore::
2538 2539
2539 2540 hg forget "set:hgignore()"
2540 2541
2541 2542 Returns 0 on success.
2542 2543 """
2543 2544
2544 2545 if not pats:
2545 2546 raise util.Abort(_('no files specified'))
2546 2547
2547 2548 m = scmutil.match(repo[None], pats, opts)
2548 2549 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2549 2550 return rejected and 1 or 0
2550 2551
2551 2552 @command(
2552 2553 'graft',
2553 2554 [('c', 'continue', False, _('resume interrupted graft')),
2554 2555 ('e', 'edit', False, _('invoke editor on commit messages')),
2555 2556 ('D', 'currentdate', False,
2556 2557 _('record the current date as commit date')),
2557 2558 ('U', 'currentuser', False,
2558 2559 _('record the current user as committer'), _('DATE'))]
2559 2560 + commitopts2 + mergetoolopts + dryrunopts,
2560 2561 _('[OPTION]... REVISION...'))
2561 2562 def graft(ui, repo, *revs, **opts):
2562 2563 '''copy changes from other branches onto the current branch
2563 2564
2564 2565 This command uses Mercurial's merge logic to copy individual
2565 2566 changes from other branches without merging branches in the
2566 2567 history graph. This is sometimes known as 'backporting' or
2567 2568 'cherry-picking'. By default, graft will copy user, date, and
2568 2569 description from the source changesets.
2569 2570
2570 2571 Changesets that are ancestors of the current revision, that have
2571 2572 already been grafted, or that are merges will be skipped.
2572 2573
2573 2574 If a graft merge results in conflicts, the graft process is
2574 2575 interrupted so that the current merge can be manually resolved.
2575 2576 Once all conflicts are addressed, the graft process can be
2576 2577 continued with the -c/--continue option.
2577 2578
2578 2579 .. note::
2579 2580 The -c/--continue option does not reapply earlier options.
2580 2581
2581 2582 .. container:: verbose
2582 2583
2583 2584 Examples:
2584 2585
2585 2586 - copy a single change to the stable branch and edit its description::
2586 2587
2587 2588 hg update stable
2588 2589 hg graft --edit 9393
2589 2590
2590 2591 - graft a range of changesets with one exception, updating dates::
2591 2592
2592 2593 hg graft -D "2085::2093 and not 2091"
2593 2594
2594 2595 - continue a graft after resolving conflicts::
2595 2596
2596 2597 hg graft -c
2597 2598
2598 2599 - show the source of a grafted changeset::
2599 2600
2600 2601 hg log --debug -r tip
2601 2602
2602 2603 Returns 0 on successful completion.
2603 2604 '''
2604 2605
2605 2606 if not opts.get('user') and opts.get('currentuser'):
2606 2607 opts['user'] = ui.username()
2607 2608 if not opts.get('date') and opts.get('currentdate'):
2608 2609 opts['date'] = "%d %d" % util.makedate()
2609 2610
2610 2611 editor = None
2611 2612 if opts.get('edit'):
2612 2613 editor = cmdutil.commitforceeditor
2613 2614
2614 2615 cont = False
2615 2616 if opts['continue']:
2616 2617 cont = True
2617 2618 if revs:
2618 2619 raise util.Abort(_("can't specify --continue and revisions"))
2619 2620 # read in unfinished revisions
2620 2621 try:
2621 2622 nodes = repo.opener.read('graftstate').splitlines()
2622 2623 revs = [repo[node].rev() for node in nodes]
2623 2624 except IOError, inst:
2624 2625 if inst.errno != errno.ENOENT:
2625 2626 raise
2626 2627 raise util.Abort(_("no graft state found, can't continue"))
2627 2628 else:
2628 2629 cmdutil.bailifchanged(repo)
2629 2630 if not revs:
2630 2631 raise util.Abort(_('no revisions specified'))
2631 2632 revs = scmutil.revrange(repo, revs)
2632 2633
2633 2634 # check for merges
2634 2635 for rev in repo.revs('%ld and merge()', revs):
2635 2636 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2636 2637 revs.remove(rev)
2637 2638 if not revs:
2638 2639 return -1
2639 2640
2640 2641 # check for ancestors of dest branch
2641 2642 for rev in repo.revs('::. and %ld', revs):
2642 2643 ui.warn(_('skipping ancestor revision %s\n') % rev)
2643 2644 revs.remove(rev)
2644 2645 if not revs:
2645 2646 return -1
2646 2647
2647 2648 # analyze revs for earlier grafts
2648 2649 ids = {}
2649 2650 for ctx in repo.set("%ld", revs):
2650 2651 ids[ctx.hex()] = ctx.rev()
2651 2652 n = ctx.extra().get('source')
2652 2653 if n:
2653 2654 ids[n] = ctx.rev()
2654 2655
2655 2656 # check ancestors for earlier grafts
2656 2657 ui.debug('scanning for duplicate grafts\n')
2657 2658 for ctx in repo.set("::. - ::%ld", revs):
2658 2659 n = ctx.extra().get('source')
2659 2660 if n in ids:
2660 2661 r = repo[n].rev()
2661 2662 if r in revs:
2662 2663 ui.warn(_('skipping already grafted revision %s\n') % r)
2663 2664 revs.remove(r)
2664 2665 elif ids[n] in revs:
2665 2666 ui.warn(_('skipping already grafted revision %s '
2666 2667 '(same origin %d)\n') % (ids[n], r))
2667 2668 revs.remove(ids[n])
2668 2669 elif ctx.hex() in ids:
2669 2670 r = ids[ctx.hex()]
2670 2671 ui.warn(_('skipping already grafted revision %s '
2671 2672 '(was grafted from %d)\n') % (r, ctx.rev()))
2672 2673 revs.remove(r)
2673 2674 if not revs:
2674 2675 return -1
2675 2676
2676 2677 wlock = repo.wlock()
2677 2678 try:
2678 2679 for pos, ctx in enumerate(repo.set("%ld", revs)):
2679 2680 current = repo['.']
2680 2681
2681 2682 ui.status(_('grafting revision %s\n') % ctx.rev())
2682 2683 if opts.get('dry_run'):
2683 2684 continue
2684 2685
2685 2686 # we don't merge the first commit when continuing
2686 2687 if not cont:
2687 2688 # perform the graft merge with p1(rev) as 'ancestor'
2688 2689 try:
2689 2690 # ui.forcemerge is an internal variable, do not document
2690 2691 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2691 2692 stats = mergemod.update(repo, ctx.node(), True, True, False,
2692 2693 ctx.p1().node())
2693 2694 finally:
2694 2695 ui.setconfig('ui', 'forcemerge', '')
2695 2696 # drop the second merge parent
2696 2697 repo.setparents(current.node(), nullid)
2697 2698 repo.dirstate.write()
2698 2699 # fix up dirstate for copies and renames
2699 2700 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2700 2701 # report any conflicts
2701 2702 if stats and stats[3] > 0:
2702 2703 # write out state for --continue
2703 2704 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2704 2705 repo.opener.write('graftstate', ''.join(nodelines))
2705 2706 raise util.Abort(
2706 2707 _("unresolved conflicts, can't continue"),
2707 2708 hint=_('use hg resolve and hg graft --continue'))
2708 2709 else:
2709 2710 cont = False
2710 2711
2711 2712 # commit
2712 2713 source = ctx.extra().get('source')
2713 2714 if not source:
2714 2715 source = ctx.hex()
2715 2716 extra = {'source': source}
2716 2717 user = ctx.user()
2717 2718 if opts.get('user'):
2718 2719 user = opts['user']
2719 2720 date = ctx.date()
2720 2721 if opts.get('date'):
2721 2722 date = opts['date']
2722 2723 node = repo.commit(text=ctx.description(), user=user,
2723 2724 date=date, extra=extra, editor=editor)
2724 2725 if node is None:
2725 2726 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2726 2727 finally:
2727 2728 wlock.release()
2728 2729
2729 2730 # remove state when we complete successfully
2730 2731 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2731 2732 util.unlinkpath(repo.join('graftstate'))
2732 2733
2733 2734 return 0
2734 2735
2735 2736 @command('grep',
2736 2737 [('0', 'print0', None, _('end fields with NUL')),
2737 2738 ('', 'all', None, _('print all revisions that match')),
2738 2739 ('a', 'text', None, _('treat all files as text')),
2739 2740 ('f', 'follow', None,
2740 2741 _('follow changeset history,'
2741 2742 ' or file history across copies and renames')),
2742 2743 ('i', 'ignore-case', None, _('ignore case when matching')),
2743 2744 ('l', 'files-with-matches', None,
2744 2745 _('print only filenames and revisions that match')),
2745 2746 ('n', 'line-number', None, _('print matching line numbers')),
2746 2747 ('r', 'rev', [],
2747 2748 _('only search files changed within revision range'), _('REV')),
2748 2749 ('u', 'user', None, _('list the author (long with -v)')),
2749 2750 ('d', 'date', None, _('list the date (short with -q)')),
2750 2751 ] + walkopts,
2751 2752 _('[OPTION]... PATTERN [FILE]...'))
2752 2753 def grep(ui, repo, pattern, *pats, **opts):
2753 2754 """search for a pattern in specified files and revisions
2754 2755
2755 2756 Search revisions of files for a regular expression.
2756 2757
2757 2758 This command behaves differently than Unix grep. It only accepts
2758 2759 Python/Perl regexps. It searches repository history, not the
2759 2760 working directory. It always prints the revision number in which a
2760 2761 match appears.
2761 2762
2762 2763 By default, grep only prints output for the first revision of a
2763 2764 file in which it finds a match. To get it to print every revision
2764 2765 that contains a change in match status ("-" for a match that
2765 2766 becomes a non-match, or "+" for a non-match that becomes a match),
2766 2767 use the --all flag.
2767 2768
2768 2769 Returns 0 if a match is found, 1 otherwise.
2769 2770 """
2770 2771 reflags = re.M
2771 2772 if opts.get('ignore_case'):
2772 2773 reflags |= re.I
2773 2774 try:
2774 2775 regexp = re.compile(pattern, reflags)
2775 2776 except re.error, inst:
2776 2777 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2777 2778 return 1
2778 2779 sep, eol = ':', '\n'
2779 2780 if opts.get('print0'):
2780 2781 sep = eol = '\0'
2781 2782
2782 2783 getfile = util.lrucachefunc(repo.file)
2783 2784
2784 2785 def matchlines(body):
2785 2786 begin = 0
2786 2787 linenum = 0
2787 2788 while True:
2788 2789 match = regexp.search(body, begin)
2789 2790 if not match:
2790 2791 break
2791 2792 mstart, mend = match.span()
2792 2793 linenum += body.count('\n', begin, mstart) + 1
2793 2794 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2794 2795 begin = body.find('\n', mend) + 1 or len(body) + 1
2795 2796 lend = begin - 1
2796 2797 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2797 2798
2798 2799 class linestate(object):
2799 2800 def __init__(self, line, linenum, colstart, colend):
2800 2801 self.line = line
2801 2802 self.linenum = linenum
2802 2803 self.colstart = colstart
2803 2804 self.colend = colend
2804 2805
2805 2806 def __hash__(self):
2806 2807 return hash((self.linenum, self.line))
2807 2808
2808 2809 def __eq__(self, other):
2809 2810 return self.line == other.line
2810 2811
2811 2812 matches = {}
2812 2813 copies = {}
2813 2814 def grepbody(fn, rev, body):
2814 2815 matches[rev].setdefault(fn, [])
2815 2816 m = matches[rev][fn]
2816 2817 for lnum, cstart, cend, line in matchlines(body):
2817 2818 s = linestate(line, lnum, cstart, cend)
2818 2819 m.append(s)
2819 2820
2820 2821 def difflinestates(a, b):
2821 2822 sm = difflib.SequenceMatcher(None, a, b)
2822 2823 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2823 2824 if tag == 'insert':
2824 2825 for i in xrange(blo, bhi):
2825 2826 yield ('+', b[i])
2826 2827 elif tag == 'delete':
2827 2828 for i in xrange(alo, ahi):
2828 2829 yield ('-', a[i])
2829 2830 elif tag == 'replace':
2830 2831 for i in xrange(alo, ahi):
2831 2832 yield ('-', a[i])
2832 2833 for i in xrange(blo, bhi):
2833 2834 yield ('+', b[i])
2834 2835
2835 2836 def display(fn, ctx, pstates, states):
2836 2837 rev = ctx.rev()
2837 2838 datefunc = ui.quiet and util.shortdate or util.datestr
2838 2839 found = False
2839 2840 filerevmatches = {}
2840 2841 def binary():
2841 2842 flog = getfile(fn)
2842 2843 return util.binary(flog.read(ctx.filenode(fn)))
2843 2844
2844 2845 if opts.get('all'):
2845 2846 iter = difflinestates(pstates, states)
2846 2847 else:
2847 2848 iter = [('', l) for l in states]
2848 2849 for change, l in iter:
2849 2850 cols = [fn, str(rev)]
2850 2851 before, match, after = None, None, None
2851 2852 if opts.get('line_number'):
2852 2853 cols.append(str(l.linenum))
2853 2854 if opts.get('all'):
2854 2855 cols.append(change)
2855 2856 if opts.get('user'):
2856 2857 cols.append(ui.shortuser(ctx.user()))
2857 2858 if opts.get('date'):
2858 2859 cols.append(datefunc(ctx.date()))
2859 2860 if opts.get('files_with_matches'):
2860 2861 c = (fn, rev)
2861 2862 if c in filerevmatches:
2862 2863 continue
2863 2864 filerevmatches[c] = 1
2864 2865 else:
2865 2866 before = l.line[:l.colstart]
2866 2867 match = l.line[l.colstart:l.colend]
2867 2868 after = l.line[l.colend:]
2868 2869 ui.write(sep.join(cols))
2869 2870 if before is not None:
2870 2871 if not opts.get('text') and binary():
2871 2872 ui.write(sep + " Binary file matches")
2872 2873 else:
2873 2874 ui.write(sep + before)
2874 2875 ui.write(match, label='grep.match')
2875 2876 ui.write(after)
2876 2877 ui.write(eol)
2877 2878 found = True
2878 2879 return found
2879 2880
2880 2881 skip = {}
2881 2882 revfiles = {}
2882 2883 matchfn = scmutil.match(repo[None], pats, opts)
2883 2884 found = False
2884 2885 follow = opts.get('follow')
2885 2886
2886 2887 def prep(ctx, fns):
2887 2888 rev = ctx.rev()
2888 2889 pctx = ctx.p1()
2889 2890 parent = pctx.rev()
2890 2891 matches.setdefault(rev, {})
2891 2892 matches.setdefault(parent, {})
2892 2893 files = revfiles.setdefault(rev, [])
2893 2894 for fn in fns:
2894 2895 flog = getfile(fn)
2895 2896 try:
2896 2897 fnode = ctx.filenode(fn)
2897 2898 except error.LookupError:
2898 2899 continue
2899 2900
2900 2901 copied = flog.renamed(fnode)
2901 2902 copy = follow and copied and copied[0]
2902 2903 if copy:
2903 2904 copies.setdefault(rev, {})[fn] = copy
2904 2905 if fn in skip:
2905 2906 if copy:
2906 2907 skip[copy] = True
2907 2908 continue
2908 2909 files.append(fn)
2909 2910
2910 2911 if fn not in matches[rev]:
2911 2912 grepbody(fn, rev, flog.read(fnode))
2912 2913
2913 2914 pfn = copy or fn
2914 2915 if pfn not in matches[parent]:
2915 2916 try:
2916 2917 fnode = pctx.filenode(pfn)
2917 2918 grepbody(pfn, parent, flog.read(fnode))
2918 2919 except error.LookupError:
2919 2920 pass
2920 2921
2921 2922 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2922 2923 rev = ctx.rev()
2923 2924 parent = ctx.p1().rev()
2924 2925 for fn in sorted(revfiles.get(rev, [])):
2925 2926 states = matches[rev][fn]
2926 2927 copy = copies.get(rev, {}).get(fn)
2927 2928 if fn in skip:
2928 2929 if copy:
2929 2930 skip[copy] = True
2930 2931 continue
2931 2932 pstates = matches.get(parent, {}).get(copy or fn, [])
2932 2933 if pstates or states:
2933 2934 r = display(fn, ctx, pstates, states)
2934 2935 found = found or r
2935 2936 if r and not opts.get('all'):
2936 2937 skip[fn] = True
2937 2938 if copy:
2938 2939 skip[copy] = True
2939 2940 del matches[rev]
2940 2941 del revfiles[rev]
2941 2942
2942 2943 return not found
2943 2944
2944 2945 @command('heads',
2945 2946 [('r', 'rev', '',
2946 2947 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2947 2948 ('t', 'topo', False, _('show topological heads only')),
2948 2949 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2949 2950 ('c', 'closed', False, _('show normal and closed branch heads')),
2950 2951 ] + templateopts,
2951 2952 _('[-ac] [-r STARTREV] [REV]...'))
2952 2953 def heads(ui, repo, *branchrevs, **opts):
2953 2954 """show current repository heads or show branch heads
2954 2955
2955 2956 With no arguments, show all repository branch heads.
2956 2957
2957 2958 Repository "heads" are changesets with no child changesets. They are
2958 2959 where development generally takes place and are the usual targets
2959 2960 for update and merge operations. Branch heads are changesets that have
2960 2961 no child changeset on the same branch.
2961 2962
2962 2963 If one or more REVs are given, only branch heads on the branches
2963 2964 associated with the specified changesets are shown. This means
2964 2965 that you can use :hg:`heads foo` to see the heads on a branch
2965 2966 named ``foo``.
2966 2967
2967 2968 If -c/--closed is specified, also show branch heads marked closed
2968 2969 (see :hg:`commit --close-branch`).
2969 2970
2970 2971 If STARTREV is specified, only those heads that are descendants of
2971 2972 STARTREV will be displayed.
2972 2973
2973 2974 If -t/--topo is specified, named branch mechanics will be ignored and only
2974 2975 changesets without children will be shown.
2975 2976
2976 2977 Returns 0 if matching heads are found, 1 if not.
2977 2978 """
2978 2979
2979 2980 start = None
2980 2981 if 'rev' in opts:
2981 2982 start = scmutil.revsingle(repo, opts['rev'], None).node()
2982 2983
2983 2984 if opts.get('topo'):
2984 2985 heads = [repo[h] for h in repo.heads(start)]
2985 2986 else:
2986 2987 heads = []
2987 2988 for branch in repo.branchmap():
2988 2989 heads += repo.branchheads(branch, start, opts.get('closed'))
2989 2990 heads = [repo[h] for h in heads]
2990 2991
2991 2992 if branchrevs:
2992 2993 branches = set(repo[br].branch() for br in branchrevs)
2993 2994 heads = [h for h in heads if h.branch() in branches]
2994 2995
2995 2996 if opts.get('active') and branchrevs:
2996 2997 dagheads = repo.heads(start)
2997 2998 heads = [h for h in heads if h.node() in dagheads]
2998 2999
2999 3000 if branchrevs:
3000 3001 haveheads = set(h.branch() for h in heads)
3001 3002 if branches - haveheads:
3002 3003 headless = ', '.join(b for b in branches - haveheads)
3003 3004 msg = _('no open branch heads found on branches %s')
3004 3005 if opts.get('rev'):
3005 3006 msg += _(' (started at %s)') % opts['rev']
3006 3007 ui.warn((msg + '\n') % headless)
3007 3008
3008 3009 if not heads:
3009 3010 return 1
3010 3011
3011 3012 heads = sorted(heads, key=lambda x: -x.rev())
3012 3013 displayer = cmdutil.show_changeset(ui, repo, opts)
3013 3014 for ctx in heads:
3014 3015 displayer.show(ctx)
3015 3016 displayer.close()
3016 3017
3017 3018 @command('help',
3018 3019 [('e', 'extension', None, _('show only help for extensions')),
3019 3020 ('c', 'command', None, _('show only help for commands'))],
3020 3021 _('[-ec] [TOPIC]'))
3021 3022 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3022 3023 """show help for a given topic or a help overview
3023 3024
3024 3025 With no arguments, print a list of commands with short help messages.
3025 3026
3026 3027 Given a topic, extension, or command name, print help for that
3027 3028 topic.
3028 3029
3029 3030 Returns 0 if successful.
3030 3031 """
3031 3032
3032 3033 textwidth = min(ui.termwidth(), 80) - 2
3033 3034
3034 3035 def optrst(options):
3035 3036 data = []
3036 3037 multioccur = False
3037 3038 for option in options:
3038 3039 if len(option) == 5:
3039 3040 shortopt, longopt, default, desc, optlabel = option
3040 3041 else:
3041 3042 shortopt, longopt, default, desc = option
3042 3043 optlabel = _("VALUE") # default label
3043 3044
3044 3045 if _("DEPRECATED") in desc and not ui.verbose:
3045 3046 continue
3046 3047
3047 3048 so = ''
3048 3049 if shortopt:
3049 3050 so = '-' + shortopt
3050 3051 lo = '--' + longopt
3051 3052 if default:
3052 3053 desc += _(" (default: %s)") % default
3053 3054
3054 3055 if isinstance(default, list):
3055 3056 lo += " %s [+]" % optlabel
3056 3057 multioccur = True
3057 3058 elif (default is not None) and not isinstance(default, bool):
3058 3059 lo += " %s" % optlabel
3059 3060
3060 3061 data.append((so, lo, desc))
3061 3062
3062 3063 rst = minirst.maketable(data, 1)
3063 3064
3064 3065 if multioccur:
3065 3066 rst += _("\n[+] marked option can be specified multiple times\n")
3066 3067
3067 3068 return rst
3068 3069
3069 3070 # list all option lists
3070 3071 def opttext(optlist, width):
3071 3072 rst = ''
3072 3073 if not optlist:
3073 3074 return ''
3074 3075
3075 3076 for title, options in optlist:
3076 3077 rst += '\n%s\n' % title
3077 3078 if options:
3078 3079 rst += "\n"
3079 3080 rst += optrst(options)
3080 3081 rst += '\n'
3081 3082
3082 3083 return '\n' + minirst.format(rst, width)
3083 3084
3084 3085 def addglobalopts(optlist, aliases):
3085 3086 if ui.quiet:
3086 3087 return []
3087 3088
3088 3089 if ui.verbose:
3089 3090 optlist.append((_("global options:"), globalopts))
3090 3091 if name == 'shortlist':
3091 3092 optlist.append((_('use "hg help" for the full list '
3092 3093 'of commands'), ()))
3093 3094 else:
3094 3095 if name == 'shortlist':
3095 3096 msg = _('use "hg help" for the full list of commands '
3096 3097 'or "hg -v" for details')
3097 3098 elif name and not full:
3098 3099 msg = _('use "hg help %s" to show the full help text') % name
3099 3100 elif aliases:
3100 3101 msg = _('use "hg -v help%s" to show builtin aliases and '
3101 3102 'global options') % (name and " " + name or "")
3102 3103 else:
3103 3104 msg = _('use "hg -v help %s" to show more info') % name
3104 3105 optlist.append((msg, ()))
3105 3106
3106 3107 def helpcmd(name):
3107 3108 try:
3108 3109 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3109 3110 except error.AmbiguousCommand, inst:
3110 3111 # py3k fix: except vars can't be used outside the scope of the
3111 3112 # except block, nor can be used inside a lambda. python issue4617
3112 3113 prefix = inst.args[0]
3113 3114 select = lambda c: c.lstrip('^').startswith(prefix)
3114 3115 helplist(select)
3115 3116 return
3116 3117
3117 3118 # check if it's an invalid alias and display its error if it is
3118 3119 if getattr(entry[0], 'badalias', False):
3119 3120 if not unknowncmd:
3120 3121 entry[0](ui)
3121 3122 return
3122 3123
3123 3124 rst = ""
3124 3125
3125 3126 # synopsis
3126 3127 if len(entry) > 2:
3127 3128 if entry[2].startswith('hg'):
3128 3129 rst += "%s\n" % entry[2]
3129 3130 else:
3130 3131 rst += 'hg %s %s\n' % (aliases[0], entry[2])
3131 3132 else:
3132 3133 rst += 'hg %s\n' % aliases[0]
3133 3134
3134 3135 # aliases
3135 3136 if full and not ui.quiet and len(aliases) > 1:
3136 3137 rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
3137 3138
3138 3139 # description
3139 3140 doc = gettext(entry[0].__doc__)
3140 3141 if not doc:
3141 3142 doc = _("(no help text available)")
3142 3143 if util.safehasattr(entry[0], 'definition'): # aliased command
3143 3144 if entry[0].definition.startswith('!'): # shell alias
3144 3145 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3145 3146 else:
3146 3147 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3147 3148 if ui.quiet or not full:
3148 3149 doc = doc.splitlines()[0]
3149 3150 rst += "\n" + doc + "\n"
3150 3151
3151 3152 # check if this command shadows a non-trivial (multi-line)
3152 3153 # extension help text
3153 3154 try:
3154 3155 mod = extensions.find(name)
3155 3156 doc = gettext(mod.__doc__) or ''
3156 3157 if '\n' in doc.strip():
3157 3158 msg = _('use "hg help -e %s" to show help for '
3158 3159 'the %s extension') % (name, name)
3159 3160 rst += '\n%s\n' % msg
3160 3161 except KeyError:
3161 3162 pass
3162 3163
3163 3164 # options
3164 3165 if not ui.quiet and entry[1]:
3165 3166 rst += '\n'
3166 3167 rst += _("options:")
3167 3168 rst += '\n\n'
3168 3169 rst += optrst(entry[1])
3169 3170
3170 3171 if ui.verbose:
3171 3172 rst += '\n'
3172 3173 rst += _("global options:")
3173 3174 rst += '\n\n'
3174 3175 rst += optrst(globalopts)
3175 3176
3176 3177 keep = ui.verbose and ['verbose'] or []
3177 3178 formatted, pruned = minirst.format(rst, textwidth, keep=keep)
3178 3179 ui.write(formatted)
3179 3180
3180 3181 if not ui.verbose:
3181 3182 if not full:
3182 3183 ui.write(_('\nuse "hg help %s" to show the full help text\n')
3183 3184 % name)
3184 3185 elif not ui.quiet:
3185 3186 ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
3186 3187
3187 3188
3188 3189 def helplist(select=None):
3189 3190 # list of commands
3190 3191 if name == "shortlist":
3191 3192 header = _('basic commands:\n\n')
3192 3193 else:
3193 3194 header = _('list of commands:\n\n')
3194 3195
3195 3196 h = {}
3196 3197 cmds = {}
3197 3198 for c, e in table.iteritems():
3198 3199 f = c.split("|", 1)[0]
3199 3200 if select and not select(f):
3200 3201 continue
3201 3202 if (not select and name != 'shortlist' and
3202 3203 e[0].__module__ != __name__):
3203 3204 continue
3204 3205 if name == "shortlist" and not f.startswith("^"):
3205 3206 continue
3206 3207 f = f.lstrip("^")
3207 3208 if not ui.debugflag and f.startswith("debug"):
3208 3209 continue
3209 3210 doc = e[0].__doc__
3210 3211 if doc and 'DEPRECATED' in doc and not ui.verbose:
3211 3212 continue
3212 3213 doc = gettext(doc)
3213 3214 if not doc:
3214 3215 doc = _("(no help text available)")
3215 3216 h[f] = doc.splitlines()[0].rstrip()
3216 3217 cmds[f] = c.lstrip("^")
3217 3218
3218 3219 if not h:
3219 3220 ui.status(_('no commands defined\n'))
3220 3221 return
3221 3222
3222 3223 ui.status(header)
3223 3224 fns = sorted(h)
3224 3225 m = max(map(len, fns))
3225 3226 for f in fns:
3226 3227 if ui.verbose:
3227 3228 commands = cmds[f].replace("|",", ")
3228 3229 ui.write(" %s:\n %s\n"%(commands, h[f]))
3229 3230 else:
3230 3231 ui.write('%s\n' % (util.wrap(h[f], textwidth,
3231 3232 initindent=' %-*s ' % (m, f),
3232 3233 hangindent=' ' * (m + 4))))
3233 3234
3234 3235 if not name:
3235 3236 text = help.listexts(_('enabled extensions:'), extensions.enabled())
3236 3237 if text:
3237 3238 ui.write("\n%s" % minirst.format(text, textwidth))
3238 3239
3239 3240 ui.write(_("\nadditional help topics:\n\n"))
3240 3241 topics = []
3241 3242 for names, header, doc in help.helptable:
3242 3243 topics.append((sorted(names, key=len, reverse=True)[0], header))
3243 3244 topics_len = max([len(s[0]) for s in topics])
3244 3245 for t, desc in topics:
3245 3246 ui.write(" %-*s %s\n" % (topics_len, t, desc))
3246 3247
3247 3248 optlist = []
3248 3249 addglobalopts(optlist, True)
3249 3250 ui.write(opttext(optlist, textwidth))
3250 3251
3251 3252 def helptopic(name):
3252 3253 for names, header, doc in help.helptable:
3253 3254 if name in names:
3254 3255 break
3255 3256 else:
3256 3257 raise error.UnknownCommand(name)
3257 3258
3258 3259 # description
3259 3260 if not doc:
3260 3261 doc = _("(no help text available)")
3261 3262 if util.safehasattr(doc, '__call__'):
3262 3263 doc = doc()
3263 3264
3264 3265 ui.write("%s\n\n" % header)
3265 3266 ui.write("%s" % minirst.format(doc, textwidth, indent=4))
3266 3267 try:
3267 3268 cmdutil.findcmd(name, table)
3268 3269 ui.write(_('\nuse "hg help -c %s" to see help for '
3269 3270 'the %s command\n') % (name, name))
3270 3271 except error.UnknownCommand:
3271 3272 pass
3272 3273
3273 3274 def helpext(name):
3274 3275 try:
3275 3276 mod = extensions.find(name)
3276 3277 doc = gettext(mod.__doc__) or _('no help text available')
3277 3278 except KeyError:
3278 3279 mod = None
3279 3280 doc = extensions.disabledext(name)
3280 3281 if not doc:
3281 3282 raise error.UnknownCommand(name)
3282 3283
3283 3284 if '\n' not in doc:
3284 3285 head, tail = doc, ""
3285 3286 else:
3286 3287 head, tail = doc.split('\n', 1)
3287 3288 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
3288 3289 if tail:
3289 3290 ui.write(minirst.format(tail, textwidth))
3290 3291 ui.status('\n')
3291 3292
3292 3293 if mod:
3293 3294 try:
3294 3295 ct = mod.cmdtable
3295 3296 except AttributeError:
3296 3297 ct = {}
3297 3298 modcmds = set([c.split('|', 1)[0] for c in ct])
3298 3299 helplist(modcmds.__contains__)
3299 3300 else:
3300 3301 ui.write(_('use "hg help extensions" for information on enabling '
3301 3302 'extensions\n'))
3302 3303
3303 3304 def helpextcmd(name):
3304 3305 cmd, ext, mod = extensions.disabledcmd(ui, name,
3305 3306 ui.configbool('ui', 'strict'))
3306 3307 doc = gettext(mod.__doc__).splitlines()[0]
3307 3308
3308 3309 msg = help.listexts(_("'%s' is provided by the following "
3309 3310 "extension:") % cmd, {ext: doc}, indent=4)
3310 3311 ui.write(minirst.format(msg, textwidth))
3311 3312 ui.write('\n')
3312 3313 ui.write(_('use "hg help extensions" for information on enabling '
3313 3314 'extensions\n'))
3314 3315
3315 3316 if name and name != 'shortlist':
3316 3317 i = None
3317 3318 if unknowncmd:
3318 3319 queries = (helpextcmd,)
3319 3320 elif opts.get('extension'):
3320 3321 queries = (helpext,)
3321 3322 elif opts.get('command'):
3322 3323 queries = (helpcmd,)
3323 3324 else:
3324 3325 queries = (helptopic, helpcmd, helpext, helpextcmd)
3325 3326 for f in queries:
3326 3327 try:
3327 3328 f(name)
3328 3329 i = None
3329 3330 break
3330 3331 except error.UnknownCommand, inst:
3331 3332 i = inst
3332 3333 if i:
3333 3334 raise i
3334 3335 else:
3335 3336 # program name
3336 3337 ui.status(_("Mercurial Distributed SCM\n"))
3337 3338 ui.status('\n')
3338 3339 helplist()
3339 3340
3340 3341
3341 3342 @command('identify|id',
3342 3343 [('r', 'rev', '',
3343 3344 _('identify the specified revision'), _('REV')),
3344 3345 ('n', 'num', None, _('show local revision number')),
3345 3346 ('i', 'id', None, _('show global revision id')),
3346 3347 ('b', 'branch', None, _('show branch')),
3347 3348 ('t', 'tags', None, _('show tags')),
3348 3349 ('B', 'bookmarks', None, _('show bookmarks')),
3349 3350 ] + remoteopts,
3350 3351 _('[-nibtB] [-r REV] [SOURCE]'))
3351 3352 def identify(ui, repo, source=None, rev=None,
3352 3353 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3353 3354 """identify the working copy or specified revision
3354 3355
3355 3356 Print a summary identifying the repository state at REV using one or
3356 3357 two parent hash identifiers, followed by a "+" if the working
3357 3358 directory has uncommitted changes, the branch name (if not default),
3358 3359 a list of tags, and a list of bookmarks.
3359 3360
3360 3361 When REV is not given, print a summary of the current state of the
3361 3362 repository.
3362 3363
3363 3364 Specifying a path to a repository root or Mercurial bundle will
3364 3365 cause lookup to operate on that repository/bundle.
3365 3366
3366 3367 .. container:: verbose
3367 3368
3368 3369 Examples:
3369 3370
3370 3371 - generate a build identifier for the working directory::
3371 3372
3372 3373 hg id --id > build-id.dat
3373 3374
3374 3375 - find the revision corresponding to a tag::
3375 3376
3376 3377 hg id -n -r 1.3
3377 3378
3378 3379 - check the most recent revision of a remote repository::
3379 3380
3380 3381 hg id -r tip http://selenic.com/hg/
3381 3382
3382 3383 Returns 0 if successful.
3383 3384 """
3384 3385
3385 3386 if not repo and not source:
3386 3387 raise util.Abort(_("there is no Mercurial repository here "
3387 3388 "(.hg not found)"))
3388 3389
3389 3390 hexfunc = ui.debugflag and hex or short
3390 3391 default = not (num or id or branch or tags or bookmarks)
3391 3392 output = []
3392 3393 revs = []
3393 3394
3394 3395 if source:
3395 3396 source, branches = hg.parseurl(ui.expandpath(source))
3396 3397 repo = hg.peer(ui, opts, source)
3397 3398 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3398 3399
3399 3400 if not repo.local():
3400 3401 if num or branch or tags:
3401 3402 raise util.Abort(
3402 3403 _("can't query remote revision number, branch, or tags"))
3403 3404 if not rev and revs:
3404 3405 rev = revs[0]
3405 3406 if not rev:
3406 3407 rev = "tip"
3407 3408
3408 3409 remoterev = repo.lookup(rev)
3409 3410 if default or id:
3410 3411 output = [hexfunc(remoterev)]
3411 3412
3412 3413 def getbms():
3413 3414 bms = []
3414 3415
3415 3416 if 'bookmarks' in repo.listkeys('namespaces'):
3416 3417 hexremoterev = hex(remoterev)
3417 3418 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3418 3419 if bmr == hexremoterev]
3419 3420
3420 3421 return bms
3421 3422
3422 3423 if bookmarks:
3423 3424 output.extend(getbms())
3424 3425 elif default and not ui.quiet:
3425 3426 # multiple bookmarks for a single parent separated by '/'
3426 3427 bm = '/'.join(getbms())
3427 3428 if bm:
3428 3429 output.append(bm)
3429 3430 else:
3430 3431 if not rev:
3431 3432 ctx = repo[None]
3432 3433 parents = ctx.parents()
3433 3434 changed = ""
3434 3435 if default or id or num:
3435 3436 changed = util.any(repo.status()) and "+" or ""
3436 3437 if default or id:
3437 3438 output = ["%s%s" %
3438 3439 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3439 3440 if num:
3440 3441 output.append("%s%s" %
3441 3442 ('+'.join([str(p.rev()) for p in parents]), changed))
3442 3443 else:
3443 3444 ctx = scmutil.revsingle(repo, rev)
3444 3445 if default or id:
3445 3446 output = [hexfunc(ctx.node())]
3446 3447 if num:
3447 3448 output.append(str(ctx.rev()))
3448 3449
3449 3450 if default and not ui.quiet:
3450 3451 b = ctx.branch()
3451 3452 if b != 'default':
3452 3453 output.append("(%s)" % b)
3453 3454
3454 3455 # multiple tags for a single parent separated by '/'
3455 3456 t = '/'.join(ctx.tags())
3456 3457 if t:
3457 3458 output.append(t)
3458 3459
3459 3460 # multiple bookmarks for a single parent separated by '/'
3460 3461 bm = '/'.join(ctx.bookmarks())
3461 3462 if bm:
3462 3463 output.append(bm)
3463 3464 else:
3464 3465 if branch:
3465 3466 output.append(ctx.branch())
3466 3467
3467 3468 if tags:
3468 3469 output.extend(ctx.tags())
3469 3470
3470 3471 if bookmarks:
3471 3472 output.extend(ctx.bookmarks())
3472 3473
3473 3474 ui.write("%s\n" % ' '.join(output))
3474 3475
3475 3476 @command('import|patch',
3476 3477 [('p', 'strip', 1,
3477 3478 _('directory strip option for patch. This has the same '
3478 3479 'meaning as the corresponding patch option'), _('NUM')),
3479 3480 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3480 3481 ('e', 'edit', False, _('invoke editor on commit messages')),
3481 3482 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3482 3483 ('', 'no-commit', None,
3483 3484 _("don't commit, just update the working directory")),
3484 3485 ('', 'bypass', None,
3485 3486 _("apply patch without touching the working directory")),
3486 3487 ('', 'exact', None,
3487 3488 _('apply patch to the nodes from which it was generated')),
3488 3489 ('', 'import-branch', None,
3489 3490 _('use any branch information in patch (implied by --exact)'))] +
3490 3491 commitopts + commitopts2 + similarityopts,
3491 3492 _('[OPTION]... PATCH...'))
3492 3493 def import_(ui, repo, patch1=None, *patches, **opts):
3493 3494 """import an ordered set of patches
3494 3495
3495 3496 Import a list of patches and commit them individually (unless
3496 3497 --no-commit is specified).
3497 3498
3498 3499 If there are outstanding changes in the working directory, import
3499 3500 will abort unless given the -f/--force flag.
3500 3501
3501 3502 You can import a patch straight from a mail message. Even patches
3502 3503 as attachments work (to use the body part, it must have type
3503 3504 text/plain or text/x-patch). From and Subject headers of email
3504 3505 message are used as default committer and commit message. All
3505 3506 text/plain body parts before first diff are added to commit
3506 3507 message.
3507 3508
3508 3509 If the imported patch was generated by :hg:`export`, user and
3509 3510 description from patch override values from message headers and
3510 3511 body. Values given on command line with -m/--message and -u/--user
3511 3512 override these.
3512 3513
3513 3514 If --exact is specified, import will set the working directory to
3514 3515 the parent of each patch before applying it, and will abort if the
3515 3516 resulting changeset has a different ID than the one recorded in
3516 3517 the patch. This may happen due to character set problems or other
3517 3518 deficiencies in the text patch format.
3518 3519
3519 3520 Use --bypass to apply and commit patches directly to the
3520 3521 repository, not touching the working directory. Without --exact,
3521 3522 patches will be applied on top of the working directory parent
3522 3523 revision.
3523 3524
3524 3525 With -s/--similarity, hg will attempt to discover renames and
3525 3526 copies in the patch in the same way as :hg:`addremove`.
3526 3527
3527 3528 To read a patch from standard input, use "-" as the patch name. If
3528 3529 a URL is specified, the patch will be downloaded from it.
3529 3530 See :hg:`help dates` for a list of formats valid for -d/--date.
3530 3531
3531 3532 .. container:: verbose
3532 3533
3533 3534 Examples:
3534 3535
3535 3536 - import a traditional patch from a website and detect renames::
3536 3537
3537 3538 hg import -s 80 http://example.com/bugfix.patch
3538 3539
3539 3540 - import a changeset from an hgweb server::
3540 3541
3541 3542 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3542 3543
3543 3544 - import all the patches in an Unix-style mbox::
3544 3545
3545 3546 hg import incoming-patches.mbox
3546 3547
3547 3548 - attempt to exactly restore an exported changeset (not always
3548 3549 possible)::
3549 3550
3550 3551 hg import --exact proposed-fix.patch
3551 3552
3552 3553 Returns 0 on success.
3553 3554 """
3554 3555
3555 3556 if not patch1:
3556 3557 raise util.Abort(_('need at least one patch to import'))
3557 3558
3558 3559 patches = (patch1,) + patches
3559 3560
3560 3561 date = opts.get('date')
3561 3562 if date:
3562 3563 opts['date'] = util.parsedate(date)
3563 3564
3564 3565 editor = cmdutil.commiteditor
3565 3566 if opts.get('edit'):
3566 3567 editor = cmdutil.commitforceeditor
3567 3568
3568 3569 update = not opts.get('bypass')
3569 3570 if not update and opts.get('no_commit'):
3570 3571 raise util.Abort(_('cannot use --no-commit with --bypass'))
3571 3572 try:
3572 3573 sim = float(opts.get('similarity') or 0)
3573 3574 except ValueError:
3574 3575 raise util.Abort(_('similarity must be a number'))
3575 3576 if sim < 0 or sim > 100:
3576 3577 raise util.Abort(_('similarity must be between 0 and 100'))
3577 3578 if sim and not update:
3578 3579 raise util.Abort(_('cannot use --similarity with --bypass'))
3579 3580
3580 3581 if (opts.get('exact') or not opts.get('force')) and update:
3581 3582 cmdutil.bailifchanged(repo)
3582 3583
3583 3584 base = opts["base"]
3584 3585 strip = opts["strip"]
3585 3586 wlock = lock = tr = None
3586 3587 msgs = []
3587 3588
3588 3589 def checkexact(repo, n, nodeid):
3589 3590 if opts.get('exact') and hex(n) != nodeid:
3590 3591 repo.rollback()
3591 3592 raise util.Abort(_('patch is damaged or loses information'))
3592 3593
3593 3594 def tryone(ui, hunk, parents):
3594 3595 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3595 3596 patch.extract(ui, hunk)
3596 3597
3597 3598 if not tmpname:
3598 3599 return (None, None)
3599 3600 msg = _('applied to working directory')
3600 3601
3601 3602 try:
3602 3603 cmdline_message = cmdutil.logmessage(ui, opts)
3603 3604 if cmdline_message:
3604 3605 # pickup the cmdline msg
3605 3606 message = cmdline_message
3606 3607 elif message:
3607 3608 # pickup the patch msg
3608 3609 message = message.strip()
3609 3610 else:
3610 3611 # launch the editor
3611 3612 message = None
3612 3613 ui.debug('message:\n%s\n' % message)
3613 3614
3614 3615 if len(parents) == 1:
3615 3616 parents.append(repo[nullid])
3616 3617 if opts.get('exact'):
3617 3618 if not nodeid or not p1:
3618 3619 raise util.Abort(_('not a Mercurial patch'))
3619 3620 p1 = repo[p1]
3620 3621 p2 = repo[p2 or nullid]
3621 3622 elif p2:
3622 3623 try:
3623 3624 p1 = repo[p1]
3624 3625 p2 = repo[p2]
3625 3626 # Without any options, consider p2 only if the
3626 3627 # patch is being applied on top of the recorded
3627 3628 # first parent.
3628 3629 if p1 != parents[0]:
3629 3630 p1 = parents[0]
3630 3631 p2 = repo[nullid]
3631 3632 except error.RepoError:
3632 3633 p1, p2 = parents
3633 3634 else:
3634 3635 p1, p2 = parents
3635 3636
3636 3637 n = None
3637 3638 if update:
3638 3639 if p1 != parents[0]:
3639 3640 hg.clean(repo, p1.node())
3640 3641 if p2 != parents[1]:
3641 3642 repo.setparents(p1.node(), p2.node())
3642 3643
3643 3644 if opts.get('exact') or opts.get('import_branch'):
3644 3645 repo.dirstate.setbranch(branch or 'default')
3645 3646
3646 3647 files = set()
3647 3648 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3648 3649 eolmode=None, similarity=sim / 100.0)
3649 3650 files = list(files)
3650 3651 if opts.get('no_commit'):
3651 3652 if message:
3652 3653 msgs.append(message)
3653 3654 else:
3654 3655 if opts.get('exact') or p2:
3655 3656 # If you got here, you either use --force and know what
3656 3657 # you are doing or used --exact or a merge patch while
3657 3658 # being updated to its first parent.
3658 3659 m = None
3659 3660 else:
3660 3661 m = scmutil.matchfiles(repo, files or [])
3661 3662 n = repo.commit(message, opts.get('user') or user,
3662 3663 opts.get('date') or date, match=m,
3663 3664 editor=editor)
3664 3665 checkexact(repo, n, nodeid)
3665 3666 else:
3666 3667 if opts.get('exact') or opts.get('import_branch'):
3667 3668 branch = branch or 'default'
3668 3669 else:
3669 3670 branch = p1.branch()
3670 3671 store = patch.filestore()
3671 3672 try:
3672 3673 files = set()
3673 3674 try:
3674 3675 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3675 3676 files, eolmode=None)
3676 3677 except patch.PatchError, e:
3677 3678 raise util.Abort(str(e))
3678 3679 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3679 3680 message,
3680 3681 opts.get('user') or user,
3681 3682 opts.get('date') or date,
3682 3683 branch, files, store,
3683 3684 editor=cmdutil.commiteditor)
3684 3685 repo.savecommitmessage(memctx.description())
3685 3686 n = memctx.commit()
3686 3687 checkexact(repo, n, nodeid)
3687 3688 finally:
3688 3689 store.close()
3689 3690 if n:
3690 3691 # i18n: refers to a short changeset id
3691 3692 msg = _('created %s') % short(n)
3692 3693 return (msg, n)
3693 3694 finally:
3694 3695 os.unlink(tmpname)
3695 3696
3696 3697 try:
3697 3698 try:
3698 3699 wlock = repo.wlock()
3699 3700 if not opts.get('no_commit'):
3700 3701 lock = repo.lock()
3701 3702 tr = repo.transaction('import')
3702 3703 parents = repo.parents()
3703 3704 for patchurl in patches:
3704 3705 if patchurl == '-':
3705 3706 ui.status(_('applying patch from stdin\n'))
3706 3707 patchfile = ui.fin
3707 3708 patchurl = 'stdin' # for error message
3708 3709 else:
3709 3710 patchurl = os.path.join(base, patchurl)
3710 3711 ui.status(_('applying %s\n') % patchurl)
3711 3712 patchfile = url.open(ui, patchurl)
3712 3713
3713 3714 haspatch = False
3714 3715 for hunk in patch.split(patchfile):
3715 3716 (msg, node) = tryone(ui, hunk, parents)
3716 3717 if msg:
3717 3718 haspatch = True
3718 3719 ui.note(msg + '\n')
3719 3720 if update or opts.get('exact'):
3720 3721 parents = repo.parents()
3721 3722 else:
3722 3723 parents = [repo[node]]
3723 3724
3724 3725 if not haspatch:
3725 3726 raise util.Abort(_('%s: no diffs found') % patchurl)
3726 3727
3727 3728 if tr:
3728 3729 tr.close()
3729 3730 if msgs:
3730 3731 repo.savecommitmessage('\n* * *\n'.join(msgs))
3731 3732 except:
3732 3733 # wlock.release() indirectly calls dirstate.write(): since
3733 3734 # we're crashing, we do not want to change the working dir
3734 3735 # parent after all, so make sure it writes nothing
3735 3736 repo.dirstate.invalidate()
3736 3737 raise
3737 3738 finally:
3738 3739 if tr:
3739 3740 tr.release()
3740 3741 release(lock, wlock)
3741 3742
3742 3743 @command('incoming|in',
3743 3744 [('f', 'force', None,
3744 3745 _('run even if remote repository is unrelated')),
3745 3746 ('n', 'newest-first', None, _('show newest record first')),
3746 3747 ('', 'bundle', '',
3747 3748 _('file to store the bundles into'), _('FILE')),
3748 3749 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3749 3750 ('B', 'bookmarks', False, _("compare bookmarks")),
3750 3751 ('b', 'branch', [],
3751 3752 _('a specific branch you would like to pull'), _('BRANCH')),
3752 3753 ] + logopts + remoteopts + subrepoopts,
3753 3754 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3754 3755 def incoming(ui, repo, source="default", **opts):
3755 3756 """show new changesets found in source
3756 3757
3757 3758 Show new changesets found in the specified path/URL or the default
3758 3759 pull location. These are the changesets that would have been pulled
3759 3760 if a pull at the time you issued this command.
3760 3761
3761 3762 For remote repository, using --bundle avoids downloading the
3762 3763 changesets twice if the incoming is followed by a pull.
3763 3764
3764 3765 See pull for valid source format details.
3765 3766
3766 3767 Returns 0 if there are incoming changes, 1 otherwise.
3767 3768 """
3768 3769 if opts.get('bundle') and opts.get('subrepos'):
3769 3770 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3770 3771
3771 3772 if opts.get('bookmarks'):
3772 3773 source, branches = hg.parseurl(ui.expandpath(source),
3773 3774 opts.get('branch'))
3774 3775 other = hg.peer(repo, opts, source)
3775 3776 if 'bookmarks' not in other.listkeys('namespaces'):
3776 3777 ui.warn(_("remote doesn't support bookmarks\n"))
3777 3778 return 0
3778 3779 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3779 3780 return bookmarks.diff(ui, repo, other)
3780 3781
3781 3782 repo._subtoppath = ui.expandpath(source)
3782 3783 try:
3783 3784 return hg.incoming(ui, repo, source, opts)
3784 3785 finally:
3785 3786 del repo._subtoppath
3786 3787
3787 3788
3788 3789 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3789 3790 def init(ui, dest=".", **opts):
3790 3791 """create a new repository in the given directory
3791 3792
3792 3793 Initialize a new repository in the given directory. If the given
3793 3794 directory does not exist, it will be created.
3794 3795
3795 3796 If no directory is given, the current directory is used.
3796 3797
3797 3798 It is possible to specify an ``ssh://`` URL as the destination.
3798 3799 See :hg:`help urls` for more information.
3799 3800
3800 3801 Returns 0 on success.
3801 3802 """
3802 3803 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3803 3804
3804 3805 @command('locate',
3805 3806 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3806 3807 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3807 3808 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3808 3809 ] + walkopts,
3809 3810 _('[OPTION]... [PATTERN]...'))
3810 3811 def locate(ui, repo, *pats, **opts):
3811 3812 """locate files matching specific patterns
3812 3813
3813 3814 Print files under Mercurial control in the working directory whose
3814 3815 names match the given patterns.
3815 3816
3816 3817 By default, this command searches all directories in the working
3817 3818 directory. To search just the current directory and its
3818 3819 subdirectories, use "--include .".
3819 3820
3820 3821 If no patterns are given to match, this command prints the names
3821 3822 of all files under Mercurial control in the working directory.
3822 3823
3823 3824 If you want to feed the output of this command into the "xargs"
3824 3825 command, use the -0 option to both this command and "xargs". This
3825 3826 will avoid the problem of "xargs" treating single filenames that
3826 3827 contain whitespace as multiple filenames.
3827 3828
3828 3829 Returns 0 if a match is found, 1 otherwise.
3829 3830 """
3830 3831 end = opts.get('print0') and '\0' or '\n'
3831 3832 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3832 3833
3833 3834 ret = 1
3834 3835 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3835 3836 m.bad = lambda x, y: False
3836 3837 for abs in repo[rev].walk(m):
3837 3838 if not rev and abs not in repo.dirstate:
3838 3839 continue
3839 3840 if opts.get('fullpath'):
3840 3841 ui.write(repo.wjoin(abs), end)
3841 3842 else:
3842 3843 ui.write(((pats and m.rel(abs)) or abs), end)
3843 3844 ret = 0
3844 3845
3845 3846 return ret
3846 3847
3847 3848 @command('^log|history',
3848 3849 [('f', 'follow', None,
3849 3850 _('follow changeset history, or file history across copies and renames')),
3850 3851 ('', 'follow-first', None,
3851 3852 _('only follow the first parent of merge changesets (DEPRECATED)')),
3852 3853 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3853 3854 ('C', 'copies', None, _('show copied files')),
3854 3855 ('k', 'keyword', [],
3855 3856 _('do case-insensitive search for a given text'), _('TEXT')),
3856 3857 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3857 3858 ('', 'removed', None, _('include revisions where files were removed')),
3858 3859 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3859 3860 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3860 3861 ('', 'only-branch', [],
3861 3862 _('show only changesets within the given named branch (DEPRECATED)'),
3862 3863 _('BRANCH')),
3863 3864 ('b', 'branch', [],
3864 3865 _('show changesets within the given named branch'), _('BRANCH')),
3865 3866 ('P', 'prune', [],
3866 3867 _('do not display revision or any of its ancestors'), _('REV')),
3867 3868 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3868 3869 ] + logopts + walkopts,
3869 3870 _('[OPTION]... [FILE]'))
3870 3871 def log(ui, repo, *pats, **opts):
3871 3872 """show revision history of entire repository or files
3872 3873
3873 3874 Print the revision history of the specified files or the entire
3874 3875 project.
3875 3876
3876 3877 If no revision range is specified, the default is ``tip:0`` unless
3877 3878 --follow is set, in which case the working directory parent is
3878 3879 used as the starting revision.
3879 3880
3880 3881 File history is shown without following rename or copy history of
3881 3882 files. Use -f/--follow with a filename to follow history across
3882 3883 renames and copies. --follow without a filename will only show
3883 3884 ancestors or descendants of the starting revision.
3884 3885
3885 3886 By default this command prints revision number and changeset id,
3886 3887 tags, non-trivial parents, user, date and time, and a summary for
3887 3888 each commit. When the -v/--verbose switch is used, the list of
3888 3889 changed files and full commit message are shown.
3889 3890
3890 3891 .. note::
3891 3892 log -p/--patch may generate unexpected diff output for merge
3892 3893 changesets, as it will only compare the merge changeset against
3893 3894 its first parent. Also, only files different from BOTH parents
3894 3895 will appear in files:.
3895 3896
3896 3897 .. note::
3897 3898 for performance reasons, log FILE may omit duplicate changes
3898 3899 made on branches and will not show deletions. To see all
3899 3900 changes including duplicates and deletions, use the --removed
3900 3901 switch.
3901 3902
3902 3903 .. container:: verbose
3903 3904
3904 3905 Some examples:
3905 3906
3906 3907 - changesets with full descriptions and file lists::
3907 3908
3908 3909 hg log -v
3909 3910
3910 3911 - changesets ancestral to the working directory::
3911 3912
3912 3913 hg log -f
3913 3914
3914 3915 - last 10 commits on the current branch::
3915 3916
3916 3917 hg log -l 10 -b .
3917 3918
3918 3919 - changesets showing all modifications of a file, including removals::
3919 3920
3920 3921 hg log --removed file.c
3921 3922
3922 3923 - all changesets that touch a directory, with diffs, excluding merges::
3923 3924
3924 3925 hg log -Mp lib/
3925 3926
3926 3927 - all revision numbers that match a keyword::
3927 3928
3928 3929 hg log -k bug --template "{rev}\\n"
3929 3930
3930 3931 - check if a given changeset is included is a tagged release::
3931 3932
3932 3933 hg log -r "a21ccf and ancestor(1.9)"
3933 3934
3934 3935 - find all changesets by some user in a date range::
3935 3936
3936 3937 hg log -k alice -d "may 2008 to jul 2008"
3937 3938
3938 3939 - summary of all changesets after the last tag::
3939 3940
3940 3941 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3941 3942
3942 3943 See :hg:`help dates` for a list of formats valid for -d/--date.
3943 3944
3944 3945 See :hg:`help revisions` and :hg:`help revsets` for more about
3945 3946 specifying revisions.
3946 3947
3947 3948 See :hg:`help templates` for more about pre-packaged styles and
3948 3949 specifying custom templates.
3949 3950
3950 3951 Returns 0 on success.
3951 3952 """
3952 3953
3953 3954 matchfn = scmutil.match(repo[None], pats, opts)
3954 3955 limit = cmdutil.loglimit(opts)
3955 3956 count = 0
3956 3957
3957 3958 getrenamed, endrev = None, None
3958 3959 if opts.get('copies'):
3959 3960 if opts.get('rev'):
3960 3961 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3961 3962 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3962 3963
3963 3964 df = False
3964 3965 if opts["date"]:
3965 3966 df = util.matchdate(opts["date"])
3966 3967
3967 3968 branches = opts.get('branch', []) + opts.get('only_branch', [])
3968 3969 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3969 3970
3970 3971 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3971 3972 def prep(ctx, fns):
3972 3973 rev = ctx.rev()
3973 3974 parents = [p for p in repo.changelog.parentrevs(rev)
3974 3975 if p != nullrev]
3975 3976 if opts.get('no_merges') and len(parents) == 2:
3976 3977 return
3977 3978 if opts.get('only_merges') and len(parents) != 2:
3978 3979 return
3979 3980 if opts.get('branch') and ctx.branch() not in opts['branch']:
3980 3981 return
3981 3982 if not opts.get('hidden') and ctx.hidden():
3982 3983 return
3983 3984 if df and not df(ctx.date()[0]):
3984 3985 return
3985 3986
3986 3987 lower = encoding.lower
3987 3988 if opts.get('user'):
3988 3989 luser = lower(ctx.user())
3989 3990 for k in [lower(x) for x in opts['user']]:
3990 3991 if (k in luser):
3991 3992 break
3992 3993 else:
3993 3994 return
3994 3995 if opts.get('keyword'):
3995 3996 luser = lower(ctx.user())
3996 3997 ldesc = lower(ctx.description())
3997 3998 lfiles = lower(" ".join(ctx.files()))
3998 3999 for k in [lower(x) for x in opts['keyword']]:
3999 4000 if (k in luser or k in ldesc or k in lfiles):
4000 4001 break
4001 4002 else:
4002 4003 return
4003 4004
4004 4005 copies = None
4005 4006 if getrenamed is not None and rev:
4006 4007 copies = []
4007 4008 for fn in ctx.files():
4008 4009 rename = getrenamed(fn, rev)
4009 4010 if rename:
4010 4011 copies.append((fn, rename[0]))
4011 4012
4012 4013 revmatchfn = None
4013 4014 if opts.get('patch') or opts.get('stat'):
4014 4015 if opts.get('follow') or opts.get('follow_first'):
4015 4016 # note: this might be wrong when following through merges
4016 4017 revmatchfn = scmutil.match(repo[None], fns, default='path')
4017 4018 else:
4018 4019 revmatchfn = matchfn
4019 4020
4020 4021 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4021 4022
4022 4023 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4023 4024 if count == limit:
4024 4025 break
4025 4026 if displayer.flush(ctx.rev()):
4026 4027 count += 1
4027 4028 displayer.close()
4028 4029
4029 4030 @command('manifest',
4030 4031 [('r', 'rev', '', _('revision to display'), _('REV')),
4031 4032 ('', 'all', False, _("list files from all revisions"))],
4032 4033 _('[-r REV]'))
4033 4034 def manifest(ui, repo, node=None, rev=None, **opts):
4034 4035 """output the current or given revision of the project manifest
4035 4036
4036 4037 Print a list of version controlled files for the given revision.
4037 4038 If no revision is given, the first parent of the working directory
4038 4039 is used, or the null revision if no revision is checked out.
4039 4040
4040 4041 With -v, print file permissions, symlink and executable bits.
4041 4042 With --debug, print file revision hashes.
4042 4043
4043 4044 If option --all is specified, the list of all files from all revisions
4044 4045 is printed. This includes deleted and renamed files.
4045 4046
4046 4047 Returns 0 on success.
4047 4048 """
4048 4049 if opts.get('all'):
4049 4050 if rev or node:
4050 4051 raise util.Abort(_("can't specify a revision with --all"))
4051 4052
4052 4053 res = []
4053 4054 prefix = "data/"
4054 4055 suffix = ".i"
4055 4056 plen = len(prefix)
4056 4057 slen = len(suffix)
4057 4058 lock = repo.lock()
4058 4059 try:
4059 4060 for fn, b, size in repo.store.datafiles():
4060 4061 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4061 4062 res.append(fn[plen:-slen])
4062 4063 finally:
4063 4064 lock.release()
4064 4065 for f in sorted(res):
4065 4066 ui.write("%s\n" % f)
4066 4067 return
4067 4068
4068 4069 if rev and node:
4069 4070 raise util.Abort(_("please specify just one revision"))
4070 4071
4071 4072 if not node:
4072 4073 node = rev
4073 4074
4074 4075 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4075 4076 ctx = scmutil.revsingle(repo, node)
4076 4077 for f in ctx:
4077 4078 if ui.debugflag:
4078 4079 ui.write("%40s " % hex(ctx.manifest()[f]))
4079 4080 if ui.verbose:
4080 4081 ui.write(decor[ctx.flags(f)])
4081 4082 ui.write("%s\n" % f)
4082 4083
4083 4084 @command('^merge',
4084 4085 [('f', 'force', None, _('force a merge with outstanding changes')),
4085 4086 ('r', 'rev', '', _('revision to merge'), _('REV')),
4086 4087 ('P', 'preview', None,
4087 4088 _('review revisions to merge (no merge is performed)'))
4088 4089 ] + mergetoolopts,
4089 4090 _('[-P] [-f] [[-r] REV]'))
4090 4091 def merge(ui, repo, node=None, **opts):
4091 4092 """merge working directory with another revision
4092 4093
4093 4094 The current working directory is updated with all changes made in
4094 4095 the requested revision since the last common predecessor revision.
4095 4096
4096 4097 Files that changed between either parent are marked as changed for
4097 4098 the next commit and a commit must be performed before any further
4098 4099 updates to the repository are allowed. The next commit will have
4099 4100 two parents.
4100 4101
4101 4102 ``--tool`` can be used to specify the merge tool used for file
4102 4103 merges. It overrides the HGMERGE environment variable and your
4103 4104 configuration files. See :hg:`help merge-tools` for options.
4104 4105
4105 4106 If no revision is specified, the working directory's parent is a
4106 4107 head revision, and the current branch contains exactly one other
4107 4108 head, the other head is merged with by default. Otherwise, an
4108 4109 explicit revision with which to merge with must be provided.
4109 4110
4110 4111 :hg:`resolve` must be used to resolve unresolved files.
4111 4112
4112 4113 To undo an uncommitted merge, use :hg:`update --clean .` which
4113 4114 will check out a clean copy of the original merge parent, losing
4114 4115 all changes.
4115 4116
4116 4117 Returns 0 on success, 1 if there are unresolved files.
4117 4118 """
4118 4119
4119 4120 if opts.get('rev') and node:
4120 4121 raise util.Abort(_("please specify just one revision"))
4121 4122 if not node:
4122 4123 node = opts.get('rev')
4123 4124
4124 4125 if not node:
4125 4126 branch = repo[None].branch()
4126 4127 bheads = repo.branchheads(branch)
4127 4128 if len(bheads) > 2:
4128 4129 raise util.Abort(_("branch '%s' has %d heads - "
4129 4130 "please merge with an explicit rev")
4130 4131 % (branch, len(bheads)),
4131 4132 hint=_("run 'hg heads .' to see heads"))
4132 4133
4133 4134 parent = repo.dirstate.p1()
4134 4135 if len(bheads) == 1:
4135 4136 if len(repo.heads()) > 1:
4136 4137 raise util.Abort(_("branch '%s' has one head - "
4137 4138 "please merge with an explicit rev")
4138 4139 % branch,
4139 4140 hint=_("run 'hg heads' to see all heads"))
4140 4141 msg, hint = _('nothing to merge'), None
4141 4142 if parent != repo.lookup(branch):
4142 4143 hint = _("use 'hg update' instead")
4143 4144 raise util.Abort(msg, hint=hint)
4144 4145
4145 4146 if parent not in bheads:
4146 4147 raise util.Abort(_('working directory not at a head revision'),
4147 4148 hint=_("use 'hg update' or merge with an "
4148 4149 "explicit revision"))
4149 4150 node = parent == bheads[0] and bheads[-1] or bheads[0]
4150 4151 else:
4151 4152 node = scmutil.revsingle(repo, node).node()
4152 4153
4153 4154 if opts.get('preview'):
4154 4155 # find nodes that are ancestors of p2 but not of p1
4155 4156 p1 = repo.lookup('.')
4156 4157 p2 = repo.lookup(node)
4157 4158 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4158 4159
4159 4160 displayer = cmdutil.show_changeset(ui, repo, opts)
4160 4161 for node in nodes:
4161 4162 displayer.show(repo[node])
4162 4163 displayer.close()
4163 4164 return 0
4164 4165
4165 4166 try:
4166 4167 # ui.forcemerge is an internal variable, do not document
4167 4168 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4168 4169 return hg.merge(repo, node, force=opts.get('force'))
4169 4170 finally:
4170 4171 ui.setconfig('ui', 'forcemerge', '')
4171 4172
4172 4173 @command('outgoing|out',
4173 4174 [('f', 'force', None, _('run even when the destination is unrelated')),
4174 4175 ('r', 'rev', [],
4175 4176 _('a changeset intended to be included in the destination'), _('REV')),
4176 4177 ('n', 'newest-first', None, _('show newest record first')),
4177 4178 ('B', 'bookmarks', False, _('compare bookmarks')),
4178 4179 ('b', 'branch', [], _('a specific branch you would like to push'),
4179 4180 _('BRANCH')),
4180 4181 ] + logopts + remoteopts + subrepoopts,
4181 4182 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4182 4183 def outgoing(ui, repo, dest=None, **opts):
4183 4184 """show changesets not found in the destination
4184 4185
4185 4186 Show changesets not found in the specified destination repository
4186 4187 or the default push location. These are the changesets that would
4187 4188 be pushed if a push was requested.
4188 4189
4189 4190 See pull for details of valid destination formats.
4190 4191
4191 4192 Returns 0 if there are outgoing changes, 1 otherwise.
4192 4193 """
4193 4194
4194 4195 if opts.get('bookmarks'):
4195 4196 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4196 4197 dest, branches = hg.parseurl(dest, opts.get('branch'))
4197 4198 other = hg.peer(repo, opts, dest)
4198 4199 if 'bookmarks' not in other.listkeys('namespaces'):
4199 4200 ui.warn(_("remote doesn't support bookmarks\n"))
4200 4201 return 0
4201 4202 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4202 4203 return bookmarks.diff(ui, other, repo)
4203 4204
4204 4205 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4205 4206 try:
4206 4207 return hg.outgoing(ui, repo, dest, opts)
4207 4208 finally:
4208 4209 del repo._subtoppath
4209 4210
4210 4211 @command('parents',
4211 4212 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4212 4213 ] + templateopts,
4213 4214 _('[-r REV] [FILE]'))
4214 4215 def parents(ui, repo, file_=None, **opts):
4215 4216 """show the parents of the working directory or revision
4216 4217
4217 4218 Print the working directory's parent revisions. If a revision is
4218 4219 given via -r/--rev, the parent of that revision will be printed.
4219 4220 If a file argument is given, the revision in which the file was
4220 4221 last changed (before the working directory revision or the
4221 4222 argument to --rev if given) is printed.
4222 4223
4223 4224 Returns 0 on success.
4224 4225 """
4225 4226
4226 4227 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4227 4228
4228 4229 if file_:
4229 4230 m = scmutil.match(ctx, (file_,), opts)
4230 4231 if m.anypats() or len(m.files()) != 1:
4231 4232 raise util.Abort(_('can only specify an explicit filename'))
4232 4233 file_ = m.files()[0]
4233 4234 filenodes = []
4234 4235 for cp in ctx.parents():
4235 4236 if not cp:
4236 4237 continue
4237 4238 try:
4238 4239 filenodes.append(cp.filenode(file_))
4239 4240 except error.LookupError:
4240 4241 pass
4241 4242 if not filenodes:
4242 4243 raise util.Abort(_("'%s' not found in manifest!") % file_)
4243 4244 fl = repo.file(file_)
4244 4245 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4245 4246 else:
4246 4247 p = [cp.node() for cp in ctx.parents()]
4247 4248
4248 4249 displayer = cmdutil.show_changeset(ui, repo, opts)
4249 4250 for n in p:
4250 4251 if n != nullid:
4251 4252 displayer.show(repo[n])
4252 4253 displayer.close()
4253 4254
4254 4255 @command('paths', [], _('[NAME]'))
4255 4256 def paths(ui, repo, search=None):
4256 4257 """show aliases for remote repositories
4257 4258
4258 4259 Show definition of symbolic path name NAME. If no name is given,
4259 4260 show definition of all available names.
4260 4261
4261 4262 Option -q/--quiet suppresses all output when searching for NAME
4262 4263 and shows only the path names when listing all definitions.
4263 4264
4264 4265 Path names are defined in the [paths] section of your
4265 4266 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4266 4267 repository, ``.hg/hgrc`` is used, too.
4267 4268
4268 4269 The path names ``default`` and ``default-push`` have a special
4269 4270 meaning. When performing a push or pull operation, they are used
4270 4271 as fallbacks if no location is specified on the command-line.
4271 4272 When ``default-push`` is set, it will be used for push and
4272 4273 ``default`` will be used for pull; otherwise ``default`` is used
4273 4274 as the fallback for both. When cloning a repository, the clone
4274 4275 source is written as ``default`` in ``.hg/hgrc``. Note that
4275 4276 ``default`` and ``default-push`` apply to all inbound (e.g.
4276 4277 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4277 4278 :hg:`bundle`) operations.
4278 4279
4279 4280 See :hg:`help urls` for more information.
4280 4281
4281 4282 Returns 0 on success.
4282 4283 """
4283 4284 if search:
4284 4285 for name, path in ui.configitems("paths"):
4285 4286 if name == search:
4286 4287 ui.status("%s\n" % util.hidepassword(path))
4287 4288 return
4288 4289 if not ui.quiet:
4289 4290 ui.warn(_("not found!\n"))
4290 4291 return 1
4291 4292 else:
4292 4293 for name, path in ui.configitems("paths"):
4293 4294 if ui.quiet:
4294 4295 ui.write("%s\n" % name)
4295 4296 else:
4296 4297 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4297 4298
4298 4299 @command('^phase',
4299 4300 [('p', 'public', False, _('set changeset phase to public')),
4300 4301 ('d', 'draft', False, _('set changeset phase to draft')),
4301 4302 ('s', 'secret', False, _('set changeset phase to secret')),
4302 4303 ('f', 'force', False, _('allow to move boundary backward')),
4303 4304 ('r', 'rev', [], _('target revision'), _('REV')),
4304 4305 ],
4305 4306 _('[-p|-d|-s] [-f] [-r] REV...'))
4306 4307 def phase(ui, repo, *revs, **opts):
4307 4308 """set or show the current phase name
4308 4309
4309 4310 With no argument, show the phase name of specified revisions.
4310 4311
4311 4312 With one of -p/--public, -d/--draft or -s/--secret, change the
4312 4313 phase value of the specified revisions.
4313 4314
4314 4315 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4315 4316 lower phase to an higher phase. Phases are ordered as follows::
4316 4317
4317 4318 public < draft < secret
4318 4319
4319 4320 Return 0 on success, 1 if no phases were changed or some could not
4320 4321 be changed.
4321 4322 """
4322 4323 # search for a unique phase argument
4323 4324 targetphase = None
4324 4325 for idx, name in enumerate(phases.phasenames):
4325 4326 if opts[name]:
4326 4327 if targetphase is not None:
4327 4328 raise util.Abort(_('only one phase can be specified'))
4328 4329 targetphase = idx
4329 4330
4330 4331 # look for specified revision
4331 4332 revs = list(revs)
4332 4333 revs.extend(opts['rev'])
4333 4334 if not revs:
4334 4335 raise util.Abort(_('no revisions specified'))
4335 4336
4336 4337 revs = scmutil.revrange(repo, revs)
4337 4338
4338 4339 lock = None
4339 4340 ret = 0
4340 4341 if targetphase is None:
4341 4342 # display
4342 4343 for r in revs:
4343 4344 ctx = repo[r]
4344 4345 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4345 4346 else:
4346 4347 lock = repo.lock()
4347 4348 try:
4348 4349 # set phase
4349 4350 nodes = [ctx.node() for ctx in repo.set('%ld', revs)]
4350 4351 if not nodes:
4351 4352 raise util.Abort(_('empty revision set'))
4352 4353 olddata = repo._phaserev[:]
4353 4354 phases.advanceboundary(repo, targetphase, nodes)
4354 4355 if opts['force']:
4355 4356 phases.retractboundary(repo, targetphase, nodes)
4356 4357 finally:
4357 4358 lock.release()
4358 4359 if olddata is not None:
4359 4360 changes = 0
4360 4361 newdata = repo._phaserev
4361 4362 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4362 4363 rejected = [n for n in nodes
4363 4364 if newdata[repo[n].rev()] < targetphase]
4364 4365 if rejected:
4365 4366 ui.warn(_('cannot move %i changesets to a more permissive '
4366 4367 'phase, use --force\n') % len(rejected))
4367 4368 ret = 1
4368 4369 if changes:
4369 4370 msg = _('phase changed for %i changesets\n') % changes
4370 4371 if ret:
4371 4372 ui.status(msg)
4372 4373 else:
4373 4374 ui.note(msg)
4374 4375 else:
4375 4376 ui.warn(_('no phases changed\n'))
4376 4377 ret = 1
4377 4378 return ret
4378 4379
4379 4380 def postincoming(ui, repo, modheads, optupdate, checkout):
4380 4381 if modheads == 0:
4381 4382 return
4382 4383 if optupdate:
4383 4384 movemarkfrom = repo['.'].node()
4384 4385 try:
4385 4386 ret = hg.update(repo, checkout)
4386 4387 except util.Abort, inst:
4387 4388 ui.warn(_("not updating: %s\n") % str(inst))
4388 4389 return 0
4389 4390 if not ret and not checkout:
4390 4391 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4391 4392 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4392 4393 return ret
4393 4394 if modheads > 1:
4394 4395 currentbranchheads = len(repo.branchheads())
4395 4396 if currentbranchheads == modheads:
4396 4397 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4397 4398 elif currentbranchheads > 1:
4398 4399 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
4399 4400 else:
4400 4401 ui.status(_("(run 'hg heads' to see heads)\n"))
4401 4402 else:
4402 4403 ui.status(_("(run 'hg update' to get a working copy)\n"))
4403 4404
4404 4405 @command('^pull',
4405 4406 [('u', 'update', None,
4406 4407 _('update to new branch head if changesets were pulled')),
4407 4408 ('f', 'force', None, _('run even when remote repository is unrelated')),
4408 4409 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4409 4410 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4410 4411 ('b', 'branch', [], _('a specific branch you would like to pull'),
4411 4412 _('BRANCH')),
4412 4413 ] + remoteopts,
4413 4414 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4414 4415 def pull(ui, repo, source="default", **opts):
4415 4416 """pull changes from the specified source
4416 4417
4417 4418 Pull changes from a remote repository to a local one.
4418 4419
4419 4420 This finds all changes from the repository at the specified path
4420 4421 or URL and adds them to a local repository (the current one unless
4421 4422 -R is specified). By default, this does not update the copy of the
4422 4423 project in the working directory.
4423 4424
4424 4425 Use :hg:`incoming` if you want to see what would have been added
4425 4426 by a pull at the time you issued this command. If you then decide
4426 4427 to add those changes to the repository, you should use :hg:`pull
4427 4428 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4428 4429
4429 4430 If SOURCE is omitted, the 'default' path will be used.
4430 4431 See :hg:`help urls` for more information.
4431 4432
4432 4433 Returns 0 on success, 1 if an update had unresolved files.
4433 4434 """
4434 4435 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4435 4436 other = hg.peer(repo, opts, source)
4436 4437 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4437 4438 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4438 4439
4439 4440 if opts.get('bookmark'):
4440 4441 if not revs:
4441 4442 revs = []
4442 4443 rb = other.listkeys('bookmarks')
4443 4444 for b in opts['bookmark']:
4444 4445 if b not in rb:
4445 4446 raise util.Abort(_('remote bookmark %s not found!') % b)
4446 4447 revs.append(rb[b])
4447 4448
4448 4449 if revs:
4449 4450 try:
4450 4451 revs = [other.lookup(rev) for rev in revs]
4451 4452 except error.CapabilityError:
4452 4453 err = _("other repository doesn't support revision lookup, "
4453 4454 "so a rev cannot be specified.")
4454 4455 raise util.Abort(err)
4455 4456
4456 4457 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4457 4458 bookmarks.updatefromremote(ui, repo, other, source)
4458 4459 if checkout:
4459 4460 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4460 4461 repo._subtoppath = source
4461 4462 try:
4462 4463 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4463 4464
4464 4465 finally:
4465 4466 del repo._subtoppath
4466 4467
4467 4468 # update specified bookmarks
4468 4469 if opts.get('bookmark'):
4469 4470 for b in opts['bookmark']:
4470 4471 # explicit pull overrides local bookmark if any
4471 4472 ui.status(_("importing bookmark %s\n") % b)
4472 4473 repo._bookmarks[b] = repo[rb[b]].node()
4473 4474 bookmarks.write(repo)
4474 4475
4475 4476 return ret
4476 4477
4477 4478 @command('^push',
4478 4479 [('f', 'force', None, _('force push')),
4479 4480 ('r', 'rev', [],
4480 4481 _('a changeset intended to be included in the destination'),
4481 4482 _('REV')),
4482 4483 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4483 4484 ('b', 'branch', [],
4484 4485 _('a specific branch you would like to push'), _('BRANCH')),
4485 4486 ('', 'new-branch', False, _('allow pushing a new branch')),
4486 4487 ] + remoteopts,
4487 4488 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4488 4489 def push(ui, repo, dest=None, **opts):
4489 4490 """push changes to the specified destination
4490 4491
4491 4492 Push changesets from the local repository to the specified
4492 4493 destination.
4493 4494
4494 4495 This operation is symmetrical to pull: it is identical to a pull
4495 4496 in the destination repository from the current one.
4496 4497
4497 4498 By default, push will not allow creation of new heads at the
4498 4499 destination, since multiple heads would make it unclear which head
4499 4500 to use. In this situation, it is recommended to pull and merge
4500 4501 before pushing.
4501 4502
4502 4503 Use --new-branch if you want to allow push to create a new named
4503 4504 branch that is not present at the destination. This allows you to
4504 4505 only create a new branch without forcing other changes.
4505 4506
4506 4507 Use -f/--force to override the default behavior and push all
4507 4508 changesets on all branches.
4508 4509
4509 4510 If -r/--rev is used, the specified revision and all its ancestors
4510 4511 will be pushed to the remote repository.
4511 4512
4512 4513 Please see :hg:`help urls` for important details about ``ssh://``
4513 4514 URLs. If DESTINATION is omitted, a default path will be used.
4514 4515
4515 4516 Returns 0 if push was successful, 1 if nothing to push.
4516 4517 """
4517 4518
4518 4519 if opts.get('bookmark'):
4519 4520 for b in opts['bookmark']:
4520 4521 # translate -B options to -r so changesets get pushed
4521 4522 if b in repo._bookmarks:
4522 4523 opts.setdefault('rev', []).append(b)
4523 4524 else:
4524 4525 # if we try to push a deleted bookmark, translate it to null
4525 4526 # this lets simultaneous -r, -b options continue working
4526 4527 opts.setdefault('rev', []).append("null")
4527 4528
4528 4529 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4529 4530 dest, branches = hg.parseurl(dest, opts.get('branch'))
4530 4531 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4531 4532 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4532 4533 other = hg.peer(repo, opts, dest)
4533 4534 if revs:
4534 4535 revs = [repo.lookup(rev) for rev in revs]
4535 4536
4536 4537 repo._subtoppath = dest
4537 4538 try:
4538 4539 # push subrepos depth-first for coherent ordering
4539 4540 c = repo['']
4540 4541 subs = c.substate # only repos that are committed
4541 4542 for s in sorted(subs):
4542 4543 if c.sub(s).push(opts) == 0:
4543 4544 return False
4544 4545 finally:
4545 4546 del repo._subtoppath
4546 4547 result = repo.push(other, opts.get('force'), revs=revs,
4547 4548 newbranch=opts.get('new_branch'))
4548 4549
4549 4550 result = not result
4550 4551
4551 4552 if opts.get('bookmark'):
4552 4553 rb = other.listkeys('bookmarks')
4553 4554 for b in opts['bookmark']:
4554 4555 # explicit push overrides remote bookmark if any
4555 4556 if b in repo._bookmarks:
4556 4557 ui.status(_("exporting bookmark %s\n") % b)
4557 4558 new = repo[b].hex()
4558 4559 elif b in rb:
4559 4560 ui.status(_("deleting remote bookmark %s\n") % b)
4560 4561 new = '' # delete
4561 4562 else:
4562 4563 ui.warn(_('bookmark %s does not exist on the local '
4563 4564 'or remote repository!\n') % b)
4564 4565 return 2
4565 4566 old = rb.get(b, '')
4566 4567 r = other.pushkey('bookmarks', b, old, new)
4567 4568 if not r:
4568 4569 ui.warn(_('updating bookmark %s failed!\n') % b)
4569 4570 if not result:
4570 4571 result = 2
4571 4572
4572 4573 return result
4573 4574
4574 4575 @command('recover', [])
4575 4576 def recover(ui, repo):
4576 4577 """roll back an interrupted transaction
4577 4578
4578 4579 Recover from an interrupted commit or pull.
4579 4580
4580 4581 This command tries to fix the repository status after an
4581 4582 interrupted operation. It should only be necessary when Mercurial
4582 4583 suggests it.
4583 4584
4584 4585 Returns 0 if successful, 1 if nothing to recover or verify fails.
4585 4586 """
4586 4587 if repo.recover():
4587 4588 return hg.verify(repo)
4588 4589 return 1
4589 4590
4590 4591 @command('^remove|rm',
4591 4592 [('A', 'after', None, _('record delete for missing files')),
4592 4593 ('f', 'force', None,
4593 4594 _('remove (and delete) file even if added or modified')),
4594 4595 ] + walkopts,
4595 4596 _('[OPTION]... FILE...'))
4596 4597 def remove(ui, repo, *pats, **opts):
4597 4598 """remove the specified files on the next commit
4598 4599
4599 4600 Schedule the indicated files for removal from the current branch.
4600 4601
4601 4602 This command schedules the files to be removed at the next commit.
4602 4603 To undo a remove before that, see :hg:`revert`. To undo added
4603 4604 files, see :hg:`forget`.
4604 4605
4605 4606 .. container:: verbose
4606 4607
4607 4608 -A/--after can be used to remove only files that have already
4608 4609 been deleted, -f/--force can be used to force deletion, and -Af
4609 4610 can be used to remove files from the next revision without
4610 4611 deleting them from the working directory.
4611 4612
4612 4613 The following table details the behavior of remove for different
4613 4614 file states (columns) and option combinations (rows). The file
4614 4615 states are Added [A], Clean [C], Modified [M] and Missing [!]
4615 4616 (as reported by :hg:`status`). The actions are Warn, Remove
4616 4617 (from branch) and Delete (from disk):
4617 4618
4618 4619 ======= == == == ==
4619 4620 A C M !
4620 4621 ======= == == == ==
4621 4622 none W RD W R
4622 4623 -f R RD RD R
4623 4624 -A W W W R
4624 4625 -Af R R R R
4625 4626 ======= == == == ==
4626 4627
4627 4628 Note that remove never deletes files in Added [A] state from the
4628 4629 working directory, not even if option --force is specified.
4629 4630
4630 4631 Returns 0 on success, 1 if any warnings encountered.
4631 4632 """
4632 4633
4633 4634 ret = 0
4634 4635 after, force = opts.get('after'), opts.get('force')
4635 4636 if not pats and not after:
4636 4637 raise util.Abort(_('no files specified'))
4637 4638
4638 4639 m = scmutil.match(repo[None], pats, opts)
4639 4640 s = repo.status(match=m, clean=True)
4640 4641 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4641 4642
4642 4643 for f in m.files():
4643 4644 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4644 4645 if os.path.exists(m.rel(f)):
4645 4646 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4646 4647 ret = 1
4647 4648
4648 4649 if force:
4649 4650 list = modified + deleted + clean + added
4650 4651 elif after:
4651 4652 list = deleted
4652 4653 for f in modified + added + clean:
4653 4654 ui.warn(_('not removing %s: file still exists (use -f'
4654 4655 ' to force removal)\n') % m.rel(f))
4655 4656 ret = 1
4656 4657 else:
4657 4658 list = deleted + clean
4658 4659 for f in modified:
4659 4660 ui.warn(_('not removing %s: file is modified (use -f'
4660 4661 ' to force removal)\n') % m.rel(f))
4661 4662 ret = 1
4662 4663 for f in added:
4663 4664 ui.warn(_('not removing %s: file has been marked for add'
4664 4665 ' (use forget to undo)\n') % m.rel(f))
4665 4666 ret = 1
4666 4667
4667 4668 for f in sorted(list):
4668 4669 if ui.verbose or not m.exact(f):
4669 4670 ui.status(_('removing %s\n') % m.rel(f))
4670 4671
4671 4672 wlock = repo.wlock()
4672 4673 try:
4673 4674 if not after:
4674 4675 for f in list:
4675 4676 if f in added:
4676 4677 continue # we never unlink added files on remove
4677 4678 try:
4678 4679 util.unlinkpath(repo.wjoin(f))
4679 4680 except OSError, inst:
4680 4681 if inst.errno != errno.ENOENT:
4681 4682 raise
4682 4683 repo[None].forget(list)
4683 4684 finally:
4684 4685 wlock.release()
4685 4686
4686 4687 return ret
4687 4688
4688 4689 @command('rename|move|mv',
4689 4690 [('A', 'after', None, _('record a rename that has already occurred')),
4690 4691 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4691 4692 ] + walkopts + dryrunopts,
4692 4693 _('[OPTION]... SOURCE... DEST'))
4693 4694 def rename(ui, repo, *pats, **opts):
4694 4695 """rename files; equivalent of copy + remove
4695 4696
4696 4697 Mark dest as copies of sources; mark sources for deletion. If dest
4697 4698 is a directory, copies are put in that directory. If dest is a
4698 4699 file, there can only be one source.
4699 4700
4700 4701 By default, this command copies the contents of files as they
4701 4702 exist in the working directory. If invoked with -A/--after, the
4702 4703 operation is recorded, but no copying is performed.
4703 4704
4704 4705 This command takes effect at the next commit. To undo a rename
4705 4706 before that, see :hg:`revert`.
4706 4707
4707 4708 Returns 0 on success, 1 if errors are encountered.
4708 4709 """
4709 4710 wlock = repo.wlock(False)
4710 4711 try:
4711 4712 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4712 4713 finally:
4713 4714 wlock.release()
4714 4715
4715 4716 @command('resolve',
4716 4717 [('a', 'all', None, _('select all unresolved files')),
4717 4718 ('l', 'list', None, _('list state of files needing merge')),
4718 4719 ('m', 'mark', None, _('mark files as resolved')),
4719 4720 ('u', 'unmark', None, _('mark files as unresolved')),
4720 4721 ('n', 'no-status', None, _('hide status prefix'))]
4721 4722 + mergetoolopts + walkopts,
4722 4723 _('[OPTION]... [FILE]...'))
4723 4724 def resolve(ui, repo, *pats, **opts):
4724 4725 """redo merges or set/view the merge status of files
4725 4726
4726 4727 Merges with unresolved conflicts are often the result of
4727 4728 non-interactive merging using the ``internal:merge`` configuration
4728 4729 setting, or a command-line merge tool like ``diff3``. The resolve
4729 4730 command is used to manage the files involved in a merge, after
4730 4731 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4731 4732 working directory must have two parents). See :hg:`help
4732 4733 merge-tools` for information on configuring merge tools.
4733 4734
4734 4735 The resolve command can be used in the following ways:
4735 4736
4736 4737 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4737 4738 files, discarding any previous merge attempts. Re-merging is not
4738 4739 performed for files already marked as resolved. Use ``--all/-a``
4739 4740 to select all unresolved files. ``--tool`` can be used to specify
4740 4741 the merge tool used for the given files. It overrides the HGMERGE
4741 4742 environment variable and your configuration files. Previous file
4742 4743 contents are saved with a ``.orig`` suffix.
4743 4744
4744 4745 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4745 4746 (e.g. after having manually fixed-up the files). The default is
4746 4747 to mark all unresolved files.
4747 4748
4748 4749 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4749 4750 default is to mark all resolved files.
4750 4751
4751 4752 - :hg:`resolve -l`: list files which had or still have conflicts.
4752 4753 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4753 4754
4754 4755 Note that Mercurial will not let you commit files with unresolved
4755 4756 merge conflicts. You must use :hg:`resolve -m ...` before you can
4756 4757 commit after a conflicting merge.
4757 4758
4758 4759 Returns 0 on success, 1 if any files fail a resolve attempt.
4759 4760 """
4760 4761
4761 4762 all, mark, unmark, show, nostatus = \
4762 4763 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4763 4764
4764 4765 if (show and (mark or unmark)) or (mark and unmark):
4765 4766 raise util.Abort(_("too many options specified"))
4766 4767 if pats and all:
4767 4768 raise util.Abort(_("can't specify --all and patterns"))
4768 4769 if not (all or pats or show or mark or unmark):
4769 4770 raise util.Abort(_('no files or directories specified; '
4770 4771 'use --all to remerge all files'))
4771 4772
4772 4773 ms = mergemod.mergestate(repo)
4773 4774 m = scmutil.match(repo[None], pats, opts)
4774 4775 ret = 0
4775 4776
4776 4777 for f in ms:
4777 4778 if m(f):
4778 4779 if show:
4779 4780 if nostatus:
4780 4781 ui.write("%s\n" % f)
4781 4782 else:
4782 4783 ui.write("%s %s\n" % (ms[f].upper(), f),
4783 4784 label='resolve.' +
4784 4785 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4785 4786 elif mark:
4786 4787 ms.mark(f, "r")
4787 4788 elif unmark:
4788 4789 ms.mark(f, "u")
4789 4790 else:
4790 4791 wctx = repo[None]
4791 4792 mctx = wctx.parents()[-1]
4792 4793
4793 4794 # backup pre-resolve (merge uses .orig for its own purposes)
4794 4795 a = repo.wjoin(f)
4795 4796 util.copyfile(a, a + ".resolve")
4796 4797
4797 4798 try:
4798 4799 # resolve file
4799 4800 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4800 4801 if ms.resolve(f, wctx, mctx):
4801 4802 ret = 1
4802 4803 finally:
4803 4804 ui.setconfig('ui', 'forcemerge', '')
4804 4805
4805 4806 # replace filemerge's .orig file with our resolve file
4806 4807 util.rename(a + ".resolve", a + ".orig")
4807 4808
4808 4809 ms.commit()
4809 4810 return ret
4810 4811
4811 4812 @command('revert',
4812 4813 [('a', 'all', None, _('revert all changes when no arguments given')),
4813 4814 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4814 4815 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4815 4816 ('C', 'no-backup', None, _('do not save backup copies of files')),
4816 4817 ] + walkopts + dryrunopts,
4817 4818 _('[OPTION]... [-r REV] [NAME]...'))
4818 4819 def revert(ui, repo, *pats, **opts):
4819 4820 """restore files to their checkout state
4820 4821
4821 4822 .. note::
4822 4823 To check out earlier revisions, you should use :hg:`update REV`.
4823 4824 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4824 4825
4825 4826 With no revision specified, revert the specified files or directories
4826 4827 to the contents they had in the parent of the working directory.
4827 4828 This restores the contents of files to an unmodified
4828 4829 state and unschedules adds, removes, copies, and renames. If the
4829 4830 working directory has two parents, you must explicitly specify a
4830 4831 revision.
4831 4832
4832 4833 Using the -r/--rev or -d/--date options, revert the given files or
4833 4834 directories to their states as of a specific revision. Because
4834 4835 revert does not change the working directory parents, this will
4835 4836 cause these files to appear modified. This can be helpful to "back
4836 4837 out" some or all of an earlier change. See :hg:`backout` for a
4837 4838 related method.
4838 4839
4839 4840 Modified files are saved with a .orig suffix before reverting.
4840 4841 To disable these backups, use --no-backup.
4841 4842
4842 4843 See :hg:`help dates` for a list of formats valid for -d/--date.
4843 4844
4844 4845 Returns 0 on success.
4845 4846 """
4846 4847
4847 4848 if opts.get("date"):
4848 4849 if opts.get("rev"):
4849 4850 raise util.Abort(_("you can't specify a revision and a date"))
4850 4851 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4851 4852
4852 4853 parent, p2 = repo.dirstate.parents()
4853 4854 if not opts.get('rev') and p2 != nullid:
4854 4855 # revert after merge is a trap for new users (issue2915)
4855 4856 raise util.Abort(_('uncommitted merge with no revision specified'),
4856 4857 hint=_('use "hg update" or see "hg help revert"'))
4857 4858
4858 4859 ctx = scmutil.revsingle(repo, opts.get('rev'))
4859 4860
4860 4861 if not pats and not opts.get('all'):
4861 4862 msg = _("no files or directories specified")
4862 4863 if p2 != nullid:
4863 4864 hint = _("uncommitted merge, use --all to discard all changes,"
4864 4865 " or 'hg update -C .' to abort the merge")
4865 4866 raise util.Abort(msg, hint=hint)
4866 4867 dirty = util.any(repo.status())
4867 4868 node = ctx.node()
4868 4869 if node != parent:
4869 4870 if dirty:
4870 4871 hint = _("uncommitted changes, use --all to discard all"
4871 4872 " changes, or 'hg update %s' to update") % ctx.rev()
4872 4873 else:
4873 4874 hint = _("use --all to revert all files,"
4874 4875 " or 'hg update %s' to update") % ctx.rev()
4875 4876 elif dirty:
4876 4877 hint = _("uncommitted changes, use --all to discard all changes")
4877 4878 else:
4878 4879 hint = _("use --all to revert all files")
4879 4880 raise util.Abort(msg, hint=hint)
4880 4881
4881 4882 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4882 4883
4883 4884 @command('rollback', dryrunopts +
4884 4885 [('f', 'force', False, _('ignore safety measures'))])
4885 4886 def rollback(ui, repo, **opts):
4886 4887 """roll back the last transaction (dangerous)
4887 4888
4888 4889 This command should be used with care. There is only one level of
4889 4890 rollback, and there is no way to undo a rollback. It will also
4890 4891 restore the dirstate at the time of the last transaction, losing
4891 4892 any dirstate changes since that time. This command does not alter
4892 4893 the working directory.
4893 4894
4894 4895 Transactions are used to encapsulate the effects of all commands
4895 4896 that create new changesets or propagate existing changesets into a
4896 4897 repository. For example, the following commands are transactional,
4897 4898 and their effects can be rolled back:
4898 4899
4899 4900 - commit
4900 4901 - import
4901 4902 - pull
4902 4903 - push (with this repository as the destination)
4903 4904 - unbundle
4904 4905
4905 4906 To avoid permanent data loss, rollback will refuse to rollback a
4906 4907 commit transaction if it isn't checked out. Use --force to
4907 4908 override this protection.
4908 4909
4909 4910 This command is not intended for use on public repositories. Once
4910 4911 changes are visible for pull by other users, rolling a transaction
4911 4912 back locally is ineffective (someone else may already have pulled
4912 4913 the changes). Furthermore, a race is possible with readers of the
4913 4914 repository; for example an in-progress pull from the repository
4914 4915 may fail if a rollback is performed.
4915 4916
4916 4917 Returns 0 on success, 1 if no rollback data is available.
4917 4918 """
4918 4919 return repo.rollback(dryrun=opts.get('dry_run'),
4919 4920 force=opts.get('force'))
4920 4921
4921 4922 @command('root', [])
4922 4923 def root(ui, repo):
4923 4924 """print the root (top) of the current working directory
4924 4925
4925 4926 Print the root directory of the current repository.
4926 4927
4927 4928 Returns 0 on success.
4928 4929 """
4929 4930 ui.write(repo.root + "\n")
4930 4931
4931 4932 @command('^serve',
4932 4933 [('A', 'accesslog', '', _('name of access log file to write to'),
4933 4934 _('FILE')),
4934 4935 ('d', 'daemon', None, _('run server in background')),
4935 4936 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4936 4937 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4937 4938 # use string type, then we can check if something was passed
4938 4939 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4939 4940 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4940 4941 _('ADDR')),
4941 4942 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4942 4943 _('PREFIX')),
4943 4944 ('n', 'name', '',
4944 4945 _('name to show in web pages (default: working directory)'), _('NAME')),
4945 4946 ('', 'web-conf', '',
4946 4947 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4947 4948 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4948 4949 _('FILE')),
4949 4950 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4950 4951 ('', 'stdio', None, _('for remote clients')),
4951 4952 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4952 4953 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4953 4954 ('', 'style', '', _('template style to use'), _('STYLE')),
4954 4955 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4955 4956 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4956 4957 _('[OPTION]...'))
4957 4958 def serve(ui, repo, **opts):
4958 4959 """start stand-alone webserver
4959 4960
4960 4961 Start a local HTTP repository browser and pull server. You can use
4961 4962 this for ad-hoc sharing and browsing of repositories. It is
4962 4963 recommended to use a real web server to serve a repository for
4963 4964 longer periods of time.
4964 4965
4965 4966 Please note that the server does not implement access control.
4966 4967 This means that, by default, anybody can read from the server and
4967 4968 nobody can write to it by default. Set the ``web.allow_push``
4968 4969 option to ``*`` to allow everybody to push to the server. You
4969 4970 should use a real web server if you need to authenticate users.
4970 4971
4971 4972 By default, the server logs accesses to stdout and errors to
4972 4973 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4973 4974 files.
4974 4975
4975 4976 To have the server choose a free port number to listen on, specify
4976 4977 a port number of 0; in this case, the server will print the port
4977 4978 number it uses.
4978 4979
4979 4980 Returns 0 on success.
4980 4981 """
4981 4982
4982 4983 if opts["stdio"] and opts["cmdserver"]:
4983 4984 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4984 4985
4985 4986 def checkrepo():
4986 4987 if repo is None:
4987 4988 raise error.RepoError(_("There is no Mercurial repository here"
4988 4989 " (.hg not found)"))
4989 4990
4990 4991 if opts["stdio"]:
4991 4992 checkrepo()
4992 4993 s = sshserver.sshserver(ui, repo)
4993 4994 s.serve_forever()
4994 4995
4995 4996 if opts["cmdserver"]:
4996 4997 checkrepo()
4997 4998 s = commandserver.server(ui, repo, opts["cmdserver"])
4998 4999 return s.serve()
4999 5000
5000 5001 # this way we can check if something was given in the command-line
5001 5002 if opts.get('port'):
5002 5003 opts['port'] = util.getport(opts.get('port'))
5003 5004
5004 5005 baseui = repo and repo.baseui or ui
5005 5006 optlist = ("name templates style address port prefix ipv6"
5006 5007 " accesslog errorlog certificate encoding")
5007 5008 for o in optlist.split():
5008 5009 val = opts.get(o, '')
5009 5010 if val in (None, ''): # should check against default options instead
5010 5011 continue
5011 5012 baseui.setconfig("web", o, val)
5012 5013 if repo and repo.ui != baseui:
5013 5014 repo.ui.setconfig("web", o, val)
5014 5015
5015 5016 o = opts.get('web_conf') or opts.get('webdir_conf')
5016 5017 if not o:
5017 5018 if not repo:
5018 5019 raise error.RepoError(_("There is no Mercurial repository"
5019 5020 " here (.hg not found)"))
5020 5021 o = repo.root
5021 5022
5022 5023 app = hgweb.hgweb(o, baseui=ui)
5023 5024
5024 5025 class service(object):
5025 5026 def init(self):
5026 5027 util.setsignalhandler()
5027 5028 self.httpd = hgweb.server.create_server(ui, app)
5028 5029
5029 5030 if opts['port'] and not ui.verbose:
5030 5031 return
5031 5032
5032 5033 if self.httpd.prefix:
5033 5034 prefix = self.httpd.prefix.strip('/') + '/'
5034 5035 else:
5035 5036 prefix = ''
5036 5037
5037 5038 port = ':%d' % self.httpd.port
5038 5039 if port == ':80':
5039 5040 port = ''
5040 5041
5041 5042 bindaddr = self.httpd.addr
5042 5043 if bindaddr == '0.0.0.0':
5043 5044 bindaddr = '*'
5044 5045 elif ':' in bindaddr: # IPv6
5045 5046 bindaddr = '[%s]' % bindaddr
5046 5047
5047 5048 fqaddr = self.httpd.fqaddr
5048 5049 if ':' in fqaddr:
5049 5050 fqaddr = '[%s]' % fqaddr
5050 5051 if opts['port']:
5051 5052 write = ui.status
5052 5053 else:
5053 5054 write = ui.write
5054 5055 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5055 5056 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5056 5057
5057 5058 def run(self):
5058 5059 self.httpd.serve_forever()
5059 5060
5060 5061 service = service()
5061 5062
5062 5063 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5063 5064
5064 5065 @command('showconfig|debugconfig',
5065 5066 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5066 5067 _('[-u] [NAME]...'))
5067 5068 def showconfig(ui, repo, *values, **opts):
5068 5069 """show combined config settings from all hgrc files
5069 5070
5070 5071 With no arguments, print names and values of all config items.
5071 5072
5072 5073 With one argument of the form section.name, print just the value
5073 5074 of that config item.
5074 5075
5075 5076 With multiple arguments, print names and values of all config
5076 5077 items with matching section names.
5077 5078
5078 5079 With --debug, the source (filename and line number) is printed
5079 5080 for each config item.
5080 5081
5081 5082 Returns 0 on success.
5082 5083 """
5083 5084
5084 5085 for f in scmutil.rcpath():
5085 5086 ui.debug('read config from: %s\n' % f)
5086 5087 untrusted = bool(opts.get('untrusted'))
5087 5088 if values:
5088 5089 sections = [v for v in values if '.' not in v]
5089 5090 items = [v for v in values if '.' in v]
5090 5091 if len(items) > 1 or items and sections:
5091 5092 raise util.Abort(_('only one config item permitted'))
5092 5093 for section, name, value in ui.walkconfig(untrusted=untrusted):
5093 5094 value = str(value).replace('\n', '\\n')
5094 5095 sectname = section + '.' + name
5095 5096 if values:
5096 5097 for v in values:
5097 5098 if v == section:
5098 5099 ui.debug('%s: ' %
5099 5100 ui.configsource(section, name, untrusted))
5100 5101 ui.write('%s=%s\n' % (sectname, value))
5101 5102 elif v == sectname:
5102 5103 ui.debug('%s: ' %
5103 5104 ui.configsource(section, name, untrusted))
5104 5105 ui.write(value, '\n')
5105 5106 else:
5106 5107 ui.debug('%s: ' %
5107 5108 ui.configsource(section, name, untrusted))
5108 5109 ui.write('%s=%s\n' % (sectname, value))
5109 5110
5110 5111 @command('^status|st',
5111 5112 [('A', 'all', None, _('show status of all files')),
5112 5113 ('m', 'modified', None, _('show only modified files')),
5113 5114 ('a', 'added', None, _('show only added files')),
5114 5115 ('r', 'removed', None, _('show only removed files')),
5115 5116 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5116 5117 ('c', 'clean', None, _('show only files without changes')),
5117 5118 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5118 5119 ('i', 'ignored', None, _('show only ignored files')),
5119 5120 ('n', 'no-status', None, _('hide status prefix')),
5120 5121 ('C', 'copies', None, _('show source of copied files')),
5121 5122 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5122 5123 ('', 'rev', [], _('show difference from revision'), _('REV')),
5123 5124 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5124 5125 ] + walkopts + subrepoopts,
5125 5126 _('[OPTION]... [FILE]...'))
5126 5127 def status(ui, repo, *pats, **opts):
5127 5128 """show changed files in the working directory
5128 5129
5129 5130 Show status of files in the repository. If names are given, only
5130 5131 files that match are shown. Files that are clean or ignored or
5131 5132 the source of a copy/move operation, are not listed unless
5132 5133 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5133 5134 Unless options described with "show only ..." are given, the
5134 5135 options -mardu are used.
5135 5136
5136 5137 Option -q/--quiet hides untracked (unknown and ignored) files
5137 5138 unless explicitly requested with -u/--unknown or -i/--ignored.
5138 5139
5139 5140 .. note::
5140 5141 status may appear to disagree with diff if permissions have
5141 5142 changed or a merge has occurred. The standard diff format does
5142 5143 not report permission changes and diff only reports changes
5143 5144 relative to one merge parent.
5144 5145
5145 5146 If one revision is given, it is used as the base revision.
5146 5147 If two revisions are given, the differences between them are
5147 5148 shown. The --change option can also be used as a shortcut to list
5148 5149 the changed files of a revision from its first parent.
5149 5150
5150 5151 The codes used to show the status of files are::
5151 5152
5152 5153 M = modified
5153 5154 A = added
5154 5155 R = removed
5155 5156 C = clean
5156 5157 ! = missing (deleted by non-hg command, but still tracked)
5157 5158 ? = not tracked
5158 5159 I = ignored
5159 5160 = origin of the previous file listed as A (added)
5160 5161
5161 5162 .. container:: verbose
5162 5163
5163 5164 Examples:
5164 5165
5165 5166 - show changes in the working directory relative to a
5166 5167 changeset::
5167 5168
5168 5169 hg status --rev 9353
5169 5170
5170 5171 - show all changes including copies in an existing changeset::
5171 5172
5172 5173 hg status --copies --change 9353
5173 5174
5174 5175 - get a NUL separated list of added files, suitable for xargs::
5175 5176
5176 5177 hg status -an0
5177 5178
5178 5179 Returns 0 on success.
5179 5180 """
5180 5181
5181 5182 revs = opts.get('rev')
5182 5183 change = opts.get('change')
5183 5184
5184 5185 if revs and change:
5185 5186 msg = _('cannot specify --rev and --change at the same time')
5186 5187 raise util.Abort(msg)
5187 5188 elif change:
5188 5189 node2 = scmutil.revsingle(repo, change, None).node()
5189 5190 node1 = repo[node2].p1().node()
5190 5191 else:
5191 5192 node1, node2 = scmutil.revpair(repo, revs)
5192 5193
5193 5194 cwd = (pats and repo.getcwd()) or ''
5194 5195 end = opts.get('print0') and '\0' or '\n'
5195 5196 copy = {}
5196 5197 states = 'modified added removed deleted unknown ignored clean'.split()
5197 5198 show = [k for k in states if opts.get(k)]
5198 5199 if opts.get('all'):
5199 5200 show += ui.quiet and (states[:4] + ['clean']) or states
5200 5201 if not show:
5201 5202 show = ui.quiet and states[:4] or states[:5]
5202 5203
5203 5204 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5204 5205 'ignored' in show, 'clean' in show, 'unknown' in show,
5205 5206 opts.get('subrepos'))
5206 5207 changestates = zip(states, 'MAR!?IC', stat)
5207 5208
5208 5209 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5209 5210 copy = copies.pathcopies(repo[node1], repo[node2])
5210 5211
5211 5212 fm = ui.formatter('status', opts)
5212 5213 format = '%s %s' + end
5213 5214 if opts.get('no_status'):
5214 5215 format = '%.0s%s' + end
5215 5216
5216 5217 for state, char, files in changestates:
5217 5218 if state in show:
5218 5219 label = 'status.' + state
5219 5220 for f in files:
5220 5221 fm.startitem()
5221 5222 fm.write("status path", format, char,
5222 5223 repo.pathto(f, cwd), label=label)
5223 5224 if f in copy:
5224 5225 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5225 5226 label='status.copied')
5226 5227 fm.end()
5227 5228
5228 5229 @command('^summary|sum',
5229 5230 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5230 5231 def summary(ui, repo, **opts):
5231 5232 """summarize working directory state
5232 5233
5233 5234 This generates a brief summary of the working directory state,
5234 5235 including parents, branch, commit status, and available updates.
5235 5236
5236 5237 With the --remote option, this will check the default paths for
5237 5238 incoming and outgoing changes. This can be time-consuming.
5238 5239
5239 5240 Returns 0 on success.
5240 5241 """
5241 5242
5242 5243 ctx = repo[None]
5243 5244 parents = ctx.parents()
5244 5245 pnode = parents[0].node()
5245 5246 marks = []
5246 5247
5247 5248 for p in parents:
5248 5249 # label with log.changeset (instead of log.parent) since this
5249 5250 # shows a working directory parent *changeset*:
5250 5251 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5251 5252 label='log.changeset')
5252 5253 ui.write(' '.join(p.tags()), label='log.tag')
5253 5254 if p.bookmarks():
5254 5255 marks.extend(p.bookmarks())
5255 5256 if p.rev() == -1:
5256 5257 if not len(repo):
5257 5258 ui.write(_(' (empty repository)'))
5258 5259 else:
5259 5260 ui.write(_(' (no revision checked out)'))
5260 5261 ui.write('\n')
5261 5262 if p.description():
5262 5263 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5263 5264 label='log.summary')
5264 5265
5265 5266 branch = ctx.branch()
5266 5267 bheads = repo.branchheads(branch)
5267 5268 m = _('branch: %s\n') % branch
5268 5269 if branch != 'default':
5269 5270 ui.write(m, label='log.branch')
5270 5271 else:
5271 5272 ui.status(m, label='log.branch')
5272 5273
5273 5274 if marks:
5274 5275 current = repo._bookmarkcurrent
5275 5276 ui.write(_('bookmarks:'), label='log.bookmark')
5276 5277 if current is not None:
5277 5278 try:
5278 5279 marks.remove(current)
5279 5280 ui.write(' *' + current, label='bookmarks.current')
5280 5281 except ValueError:
5281 5282 # current bookmark not in parent ctx marks
5282 5283 pass
5283 5284 for m in marks:
5284 5285 ui.write(' ' + m, label='log.bookmark')
5285 5286 ui.write('\n', label='log.bookmark')
5286 5287
5287 5288 st = list(repo.status(unknown=True))[:6]
5288 5289
5289 5290 c = repo.dirstate.copies()
5290 5291 copied, renamed = [], []
5291 5292 for d, s in c.iteritems():
5292 5293 if s in st[2]:
5293 5294 st[2].remove(s)
5294 5295 renamed.append(d)
5295 5296 else:
5296 5297 copied.append(d)
5297 5298 if d in st[1]:
5298 5299 st[1].remove(d)
5299 5300 st.insert(3, renamed)
5300 5301 st.insert(4, copied)
5301 5302
5302 5303 ms = mergemod.mergestate(repo)
5303 5304 st.append([f for f in ms if ms[f] == 'u'])
5304 5305
5305 5306 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5306 5307 st.append(subs)
5307 5308
5308 5309 labels = [ui.label(_('%d modified'), 'status.modified'),
5309 5310 ui.label(_('%d added'), 'status.added'),
5310 5311 ui.label(_('%d removed'), 'status.removed'),
5311 5312 ui.label(_('%d renamed'), 'status.copied'),
5312 5313 ui.label(_('%d copied'), 'status.copied'),
5313 5314 ui.label(_('%d deleted'), 'status.deleted'),
5314 5315 ui.label(_('%d unknown'), 'status.unknown'),
5315 5316 ui.label(_('%d ignored'), 'status.ignored'),
5316 5317 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5317 5318 ui.label(_('%d subrepos'), 'status.modified')]
5318 5319 t = []
5319 5320 for s, l in zip(st, labels):
5320 5321 if s:
5321 5322 t.append(l % len(s))
5322 5323
5323 5324 t = ', '.join(t)
5324 5325 cleanworkdir = False
5325 5326
5326 5327 if len(parents) > 1:
5327 5328 t += _(' (merge)')
5328 5329 elif branch != parents[0].branch():
5329 5330 t += _(' (new branch)')
5330 5331 elif (parents[0].extra().get('close') and
5331 5332 pnode in repo.branchheads(branch, closed=True)):
5332 5333 t += _(' (head closed)')
5333 5334 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5334 5335 t += _(' (clean)')
5335 5336 cleanworkdir = True
5336 5337 elif pnode not in bheads:
5337 5338 t += _(' (new branch head)')
5338 5339
5339 5340 if cleanworkdir:
5340 5341 ui.status(_('commit: %s\n') % t.strip())
5341 5342 else:
5342 5343 ui.write(_('commit: %s\n') % t.strip())
5343 5344
5344 5345 # all ancestors of branch heads - all ancestors of parent = new csets
5345 5346 new = [0] * len(repo)
5346 5347 cl = repo.changelog
5347 5348 for a in [cl.rev(n) for n in bheads]:
5348 5349 new[a] = 1
5349 5350 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
5350 5351 new[a] = 1
5351 5352 for a in [p.rev() for p in parents]:
5352 5353 if a >= 0:
5353 5354 new[a] = 0
5354 5355 for a in cl.ancestors(*[p.rev() for p in parents]):
5355 5356 new[a] = 0
5356 5357 new = sum(new)
5357 5358
5358 5359 if new == 0:
5359 5360 ui.status(_('update: (current)\n'))
5360 5361 elif pnode not in bheads:
5361 5362 ui.write(_('update: %d new changesets (update)\n') % new)
5362 5363 else:
5363 5364 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5364 5365 (new, len(bheads)))
5365 5366
5366 5367 if opts.get('remote'):
5367 5368 t = []
5368 5369 source, branches = hg.parseurl(ui.expandpath('default'))
5369 5370 other = hg.peer(repo, {}, source)
5370 5371 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
5371 5372 ui.debug('comparing with %s\n' % util.hidepassword(source))
5372 5373 repo.ui.pushbuffer()
5373 5374 commoninc = discovery.findcommonincoming(repo, other)
5374 5375 _common, incoming, _rheads = commoninc
5375 5376 repo.ui.popbuffer()
5376 5377 if incoming:
5377 5378 t.append(_('1 or more incoming'))
5378 5379
5379 5380 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5380 5381 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5381 5382 if source != dest:
5382 5383 other = hg.peer(repo, {}, dest)
5383 5384 commoninc = None
5384 5385 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5385 5386 repo.ui.pushbuffer()
5386 5387 outgoing = discovery.findcommonoutgoing(repo, other,
5387 5388 commoninc=commoninc)
5388 5389 repo.ui.popbuffer()
5389 5390 o = outgoing.missing
5390 5391 if o:
5391 5392 t.append(_('%d outgoing') % len(o))
5392 5393 if 'bookmarks' in other.listkeys('namespaces'):
5393 5394 lmarks = repo.listkeys('bookmarks')
5394 5395 rmarks = other.listkeys('bookmarks')
5395 5396 diff = set(rmarks) - set(lmarks)
5396 5397 if len(diff) > 0:
5397 5398 t.append(_('%d incoming bookmarks') % len(diff))
5398 5399 diff = set(lmarks) - set(rmarks)
5399 5400 if len(diff) > 0:
5400 5401 t.append(_('%d outgoing bookmarks') % len(diff))
5401 5402
5402 5403 if t:
5403 5404 ui.write(_('remote: %s\n') % (', '.join(t)))
5404 5405 else:
5405 5406 ui.status(_('remote: (synced)\n'))
5406 5407
5407 5408 @command('tag',
5408 5409 [('f', 'force', None, _('force tag')),
5409 5410 ('l', 'local', None, _('make the tag local')),
5410 5411 ('r', 'rev', '', _('revision to tag'), _('REV')),
5411 5412 ('', 'remove', None, _('remove a tag')),
5412 5413 # -l/--local is already there, commitopts cannot be used
5413 5414 ('e', 'edit', None, _('edit commit message')),
5414 5415 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5415 5416 ] + commitopts2,
5416 5417 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5417 5418 def tag(ui, repo, name1, *names, **opts):
5418 5419 """add one or more tags for the current or given revision
5419 5420
5420 5421 Name a particular revision using <name>.
5421 5422
5422 5423 Tags are used to name particular revisions of the repository and are
5423 5424 very useful to compare different revisions, to go back to significant
5424 5425 earlier versions or to mark branch points as releases, etc. Changing
5425 5426 an existing tag is normally disallowed; use -f/--force to override.
5426 5427
5427 5428 If no revision is given, the parent of the working directory is
5428 5429 used, or tip if no revision is checked out.
5429 5430
5430 5431 To facilitate version control, distribution, and merging of tags,
5431 5432 they are stored as a file named ".hgtags" which is managed similarly
5432 5433 to other project files and can be hand-edited if necessary. This
5433 5434 also means that tagging creates a new commit. The file
5434 5435 ".hg/localtags" is used for local tags (not shared among
5435 5436 repositories).
5436 5437
5437 5438 Tag commits are usually made at the head of a branch. If the parent
5438 5439 of the working directory is not a branch head, :hg:`tag` aborts; use
5439 5440 -f/--force to force the tag commit to be based on a non-head
5440 5441 changeset.
5441 5442
5442 5443 See :hg:`help dates` for a list of formats valid for -d/--date.
5443 5444
5444 5445 Since tag names have priority over branch names during revision
5445 5446 lookup, using an existing branch name as a tag name is discouraged.
5446 5447
5447 5448 Returns 0 on success.
5448 5449 """
5449 5450 wlock = lock = None
5450 5451 try:
5451 5452 wlock = repo.wlock()
5452 5453 lock = repo.lock()
5453 5454 rev_ = "."
5454 5455 names = [t.strip() for t in (name1,) + names]
5455 5456 if len(names) != len(set(names)):
5456 5457 raise util.Abort(_('tag names must be unique'))
5457 5458 for n in names:
5458 5459 if n in ['tip', '.', 'null']:
5459 5460 raise util.Abort(_("the name '%s' is reserved") % n)
5460 5461 if not n:
5461 5462 raise util.Abort(_('tag names cannot consist entirely of '
5462 5463 'whitespace'))
5463 5464 if opts.get('rev') and opts.get('remove'):
5464 5465 raise util.Abort(_("--rev and --remove are incompatible"))
5465 5466 if opts.get('rev'):
5466 5467 rev_ = opts['rev']
5467 5468 message = opts.get('message')
5468 5469 if opts.get('remove'):
5469 5470 expectedtype = opts.get('local') and 'local' or 'global'
5470 5471 for n in names:
5471 5472 if not repo.tagtype(n):
5472 5473 raise util.Abort(_("tag '%s' does not exist") % n)
5473 5474 if repo.tagtype(n) != expectedtype:
5474 5475 if expectedtype == 'global':
5475 5476 raise util.Abort(_("tag '%s' is not a global tag") % n)
5476 5477 else:
5477 5478 raise util.Abort(_("tag '%s' is not a local tag") % n)
5478 5479 rev_ = nullid
5479 5480 if not message:
5480 5481 # we don't translate commit messages
5481 5482 message = 'Removed tag %s' % ', '.join(names)
5482 5483 elif not opts.get('force'):
5483 5484 for n in names:
5484 5485 if n in repo.tags():
5485 5486 raise util.Abort(_("tag '%s' already exists "
5486 5487 "(use -f to force)") % n)
5487 5488 if not opts.get('local'):
5488 5489 p1, p2 = repo.dirstate.parents()
5489 5490 if p2 != nullid:
5490 5491 raise util.Abort(_('uncommitted merge'))
5491 5492 bheads = repo.branchheads()
5492 5493 if not opts.get('force') and bheads and p1 not in bheads:
5493 5494 raise util.Abort(_('not at a branch head (use -f to force)'))
5494 5495 r = scmutil.revsingle(repo, rev_).node()
5495 5496
5496 5497 if not message:
5497 5498 # we don't translate commit messages
5498 5499 message = ('Added tag %s for changeset %s' %
5499 5500 (', '.join(names), short(r)))
5500 5501
5501 5502 date = opts.get('date')
5502 5503 if date:
5503 5504 date = util.parsedate(date)
5504 5505
5505 5506 if opts.get('edit'):
5506 5507 message = ui.edit(message, ui.username())
5507 5508
5508 5509 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5509 5510 finally:
5510 5511 release(lock, wlock)
5511 5512
5512 5513 @command('tags', [], '')
5513 5514 def tags(ui, repo):
5514 5515 """list repository tags
5515 5516
5516 5517 This lists both regular and local tags. When the -v/--verbose
5517 5518 switch is used, a third column "local" is printed for local tags.
5518 5519
5519 5520 Returns 0 on success.
5520 5521 """
5521 5522
5522 5523 hexfunc = ui.debugflag and hex or short
5523 5524 tagtype = ""
5524 5525
5525 5526 for t, n in reversed(repo.tagslist()):
5526 5527 if ui.quiet:
5527 5528 ui.write("%s\n" % t, label='tags.normal')
5528 5529 continue
5529 5530
5530 5531 hn = hexfunc(n)
5531 5532 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5532 5533 rev = ui.label(r, 'log.changeset')
5533 5534 spaces = " " * (30 - encoding.colwidth(t))
5534 5535
5535 5536 tag = ui.label(t, 'tags.normal')
5536 5537 if ui.verbose:
5537 5538 if repo.tagtype(t) == 'local':
5538 5539 tagtype = " local"
5539 5540 tag = ui.label(t, 'tags.local')
5540 5541 else:
5541 5542 tagtype = ""
5542 5543 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5543 5544
5544 5545 @command('tip',
5545 5546 [('p', 'patch', None, _('show patch')),
5546 5547 ('g', 'git', None, _('use git extended diff format')),
5547 5548 ] + templateopts,
5548 5549 _('[-p] [-g]'))
5549 5550 def tip(ui, repo, **opts):
5550 5551 """show the tip revision
5551 5552
5552 5553 The tip revision (usually just called the tip) is the changeset
5553 5554 most recently added to the repository (and therefore the most
5554 5555 recently changed head).
5555 5556
5556 5557 If you have just made a commit, that commit will be the tip. If
5557 5558 you have just pulled changes from another repository, the tip of
5558 5559 that repository becomes the current tip. The "tip" tag is special
5559 5560 and cannot be renamed or assigned to a different changeset.
5560 5561
5561 5562 Returns 0 on success.
5562 5563 """
5563 5564 displayer = cmdutil.show_changeset(ui, repo, opts)
5564 5565 displayer.show(repo[len(repo) - 1])
5565 5566 displayer.close()
5566 5567
5567 5568 @command('unbundle',
5568 5569 [('u', 'update', None,
5569 5570 _('update to new branch head if changesets were unbundled'))],
5570 5571 _('[-u] FILE...'))
5571 5572 def unbundle(ui, repo, fname1, *fnames, **opts):
5572 5573 """apply one or more changegroup files
5573 5574
5574 5575 Apply one or more compressed changegroup files generated by the
5575 5576 bundle command.
5576 5577
5577 5578 Returns 0 on success, 1 if an update has unresolved files.
5578 5579 """
5579 5580 fnames = (fname1,) + fnames
5580 5581
5581 5582 lock = repo.lock()
5582 5583 wc = repo['.']
5583 5584 try:
5584 5585 for fname in fnames:
5585 5586 f = url.open(ui, fname)
5586 5587 gen = changegroup.readbundle(f, fname)
5587 5588 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5588 5589 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5589 5590 finally:
5590 5591 lock.release()
5591 5592 return postincoming(ui, repo, modheads, opts.get('update'), None)
5592 5593
5593 5594 @command('^update|up|checkout|co',
5594 5595 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5595 5596 ('c', 'check', None,
5596 5597 _('update across branches if no uncommitted changes')),
5597 5598 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5598 5599 ('r', 'rev', '', _('revision'), _('REV'))],
5599 5600 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5600 5601 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5601 5602 """update working directory (or switch revisions)
5602 5603
5603 5604 Update the repository's working directory to the specified
5604 5605 changeset. If no changeset is specified, update to the tip of the
5605 5606 current named branch and move the current bookmark (see :hg:`help
5606 5607 bookmarks`).
5607 5608
5608 5609 If the changeset is not a descendant of the working directory's
5609 5610 parent, the update is aborted. With the -c/--check option, the
5610 5611 working directory is checked for uncommitted changes; if none are
5611 5612 found, the working directory is updated to the specified
5612 5613 changeset.
5613 5614
5614 5615 Update sets the working directory's parent revison to the specified
5615 5616 changeset (see :hg:`help parents`).
5616 5617
5617 5618 The following rules apply when the working directory contains
5618 5619 uncommitted changes:
5619 5620
5620 5621 1. If neither -c/--check nor -C/--clean is specified, and if
5621 5622 the requested changeset is an ancestor or descendant of
5622 5623 the working directory's parent, the uncommitted changes
5623 5624 are merged into the requested changeset and the merged
5624 5625 result is left uncommitted. If the requested changeset is
5625 5626 not an ancestor or descendant (that is, it is on another
5626 5627 branch), the update is aborted and the uncommitted changes
5627 5628 are preserved.
5628 5629
5629 5630 2. With the -c/--check option, the update is aborted and the
5630 5631 uncommitted changes are preserved.
5631 5632
5632 5633 3. With the -C/--clean option, uncommitted changes are discarded and
5633 5634 the working directory is updated to the requested changeset.
5634 5635
5635 5636 Use null as the changeset to remove the working directory (like
5636 5637 :hg:`clone -U`).
5637 5638
5638 5639 If you want to revert just one file to an older revision, use
5639 5640 :hg:`revert [-r REV] NAME`.
5640 5641
5641 5642 See :hg:`help dates` for a list of formats valid for -d/--date.
5642 5643
5643 5644 Returns 0 on success, 1 if there are unresolved files.
5644 5645 """
5645 5646 if rev and node:
5646 5647 raise util.Abort(_("please specify just one revision"))
5647 5648
5648 5649 if rev is None or rev == '':
5649 5650 rev = node
5650 5651
5651 5652 # with no argument, we also move the current bookmark, if any
5652 5653 movemarkfrom = None
5653 5654 if rev is None or node == '':
5654 5655 movemarkfrom = repo['.'].node()
5655 5656
5656 5657 # if we defined a bookmark, we have to remember the original bookmark name
5657 5658 brev = rev
5658 5659 rev = scmutil.revsingle(repo, rev, rev).rev()
5659 5660
5660 5661 if check and clean:
5661 5662 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5662 5663
5663 5664 if date:
5664 5665 if rev is not None:
5665 5666 raise util.Abort(_("you can't specify a revision and a date"))
5666 5667 rev = cmdutil.finddate(ui, repo, date)
5667 5668
5668 5669 if check:
5669 5670 c = repo[None]
5670 5671 if c.dirty(merge=False, branch=False):
5671 5672 raise util.Abort(_("uncommitted local changes"))
5672 5673 if rev is None:
5673 5674 rev = repo[repo[None].branch()].rev()
5674 5675 mergemod._checkunknown(repo, repo[None], repo[rev])
5675 5676
5676 5677 if clean:
5677 5678 ret = hg.clean(repo, rev)
5678 5679 else:
5679 5680 ret = hg.update(repo, rev)
5680 5681
5681 5682 if not ret and movemarkfrom:
5682 5683 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5683 5684 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5684 5685 elif brev in repo._bookmarks:
5685 5686 bookmarks.setcurrent(repo, brev)
5686 5687 elif brev:
5687 5688 bookmarks.unsetcurrent(repo)
5688 5689
5689 5690 return ret
5690 5691
5691 5692 @command('verify', [])
5692 5693 def verify(ui, repo):
5693 5694 """verify the integrity of the repository
5694 5695
5695 5696 Verify the integrity of the current repository.
5696 5697
5697 5698 This will perform an extensive check of the repository's
5698 5699 integrity, validating the hashes and checksums of each entry in
5699 5700 the changelog, manifest, and tracked files, as well as the
5700 5701 integrity of their crosslinks and indices.
5701 5702
5702 5703 Returns 0 on success, 1 if errors are encountered.
5703 5704 """
5704 5705 return hg.verify(repo)
5705 5706
5706 5707 @command('version', [])
5707 5708 def version_(ui):
5708 5709 """output version and copyright information"""
5709 5710 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5710 5711 % util.version())
5711 5712 ui.status(_(
5712 5713 "(see http://mercurial.selenic.com for more information)\n"
5713 5714 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5714 5715 "This is free software; see the source for copying conditions. "
5715 5716 "There is NO\nwarranty; "
5716 5717 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5717 5718 ))
5718 5719
5719 5720 norepo = ("clone init version help debugcommands debugcomplete"
5720 5721 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5721 5722 " debugknown debuggetbundle debugbundle")
5722 5723 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5723 5724 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,1186 +1,1186 b''
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #include <Python.h>
11 11 #include <ctype.h>
12 12 #include <string.h>
13 13
14 14 #include "util.h"
15 15
16 16 static int hexdigit(char c)
17 17 {
18 18 if (c >= '0' && c <= '9')
19 19 return c - '0';
20 20 if (c >= 'a' && c <= 'f')
21 21 return c - 'a' + 10;
22 22 if (c >= 'A' && c <= 'F')
23 23 return c - 'A' + 10;
24 24
25 25 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
26 26 return 0;
27 27 }
28 28
29 29 /*
30 30 * Turn a hex-encoded string into binary.
31 31 */
32 32 static PyObject *unhexlify(const char *str, int len)
33 33 {
34 34 PyObject *ret;
35 35 const char *c;
36 36 char *d;
37 37
38 38 ret = PyBytes_FromStringAndSize(NULL, len / 2);
39 39
40 40 if (!ret)
41 41 return NULL;
42 42
43 43 d = PyBytes_AsString(ret);
44 44
45 45 for (c = str; c < str + len;) {
46 46 int hi = hexdigit(*c++);
47 47 int lo = hexdigit(*c++);
48 48 *d++ = (hi << 4) | lo;
49 49 }
50 50
51 51 return ret;
52 52 }
53 53
54 54 /*
55 55 * This code assumes that a manifest is stitched together with newline
56 56 * ('\n') characters.
57 57 */
58 58 static PyObject *parse_manifest(PyObject *self, PyObject *args)
59 59 {
60 60 PyObject *mfdict, *fdict;
61 61 char *str, *cur, *start, *zero;
62 62 int len;
63 63
64 64 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
65 65 &PyDict_Type, &mfdict,
66 66 &PyDict_Type, &fdict,
67 67 &str, &len))
68 68 goto quit;
69 69
70 70 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
71 71 PyObject *file = NULL, *node = NULL;
72 72 PyObject *flags = NULL;
73 73 int nlen;
74 74
75 75 if (!*cur) {
76 76 zero = cur;
77 77 continue;
78 78 }
79 79 else if (*cur != '\n')
80 80 continue;
81 81
82 82 if (!zero) {
83 83 PyErr_SetString(PyExc_ValueError,
84 84 "manifest entry has no separator");
85 85 goto quit;
86 86 }
87 87
88 88 file = PyBytes_FromStringAndSize(start, zero - start);
89 89
90 90 if (!file)
91 91 goto bail;
92 92
93 93 nlen = cur - zero - 1;
94 94
95 95 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
96 96 if (!node)
97 97 goto bail;
98 98
99 99 if (nlen > 40) {
100 100 flags = PyBytes_FromStringAndSize(zero + 41,
101 101 nlen - 40);
102 102 if (!flags)
103 103 goto bail;
104 104
105 105 if (PyDict_SetItem(fdict, file, flags) == -1)
106 106 goto bail;
107 107 }
108 108
109 109 if (PyDict_SetItem(mfdict, file, node) == -1)
110 110 goto bail;
111 111
112 112 start = cur + 1;
113 113 zero = NULL;
114 114
115 115 Py_XDECREF(flags);
116 116 Py_XDECREF(node);
117 117 Py_XDECREF(file);
118 118 continue;
119 119 bail:
120 120 Py_XDECREF(flags);
121 121 Py_XDECREF(node);
122 122 Py_XDECREF(file);
123 123 goto quit;
124 124 }
125 125
126 126 if (len > 0 && *(cur - 1) != '\n') {
127 127 PyErr_SetString(PyExc_ValueError,
128 128 "manifest contains trailing garbage");
129 129 goto quit;
130 130 }
131 131
132 132 Py_INCREF(Py_None);
133 133 return Py_None;
134 134 quit:
135 135 return NULL;
136 136 }
137 137
138 138 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
139 139 {
140 140 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
141 141 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
142 142 char *str, *cur, *end, *cpos;
143 143 int state, mode, size, mtime;
144 144 unsigned int flen;
145 145 int len;
146 146
147 147 if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
148 148 &PyDict_Type, &dmap,
149 149 &PyDict_Type, &cmap,
150 150 &str, &len))
151 151 goto quit;
152 152
153 153 /* read parents */
154 154 if (len < 40)
155 155 goto quit;
156 156
157 157 parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
158 158 if (!parents)
159 159 goto quit;
160 160
161 161 /* read filenames */
162 162 cur = str + 40;
163 163 end = str + len;
164 164
165 165 while (cur < end - 17) {
166 166 /* unpack header */
167 167 state = *cur;
168 168 mode = getbe32(cur + 1);
169 169 size = getbe32(cur + 5);
170 170 mtime = getbe32(cur + 9);
171 171 flen = getbe32(cur + 13);
172 172 cur += 17;
173 173 if (cur + flen > end || cur + flen < cur) {
174 174 PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
175 175 goto quit;
176 176 }
177 177
178 178 entry = Py_BuildValue("ciii", state, mode, size, mtime);
179 179 if (!entry)
180 180 goto quit;
181 181 PyObject_GC_UnTrack(entry); /* don't waste time with this */
182 182
183 183 cpos = memchr(cur, 0, flen);
184 184 if (cpos) {
185 185 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
186 186 cname = PyBytes_FromStringAndSize(cpos + 1,
187 187 flen - (cpos - cur) - 1);
188 188 if (!fname || !cname ||
189 189 PyDict_SetItem(cmap, fname, cname) == -1 ||
190 190 PyDict_SetItem(dmap, fname, entry) == -1)
191 191 goto quit;
192 192 Py_DECREF(cname);
193 193 } else {
194 194 fname = PyBytes_FromStringAndSize(cur, flen);
195 195 if (!fname ||
196 196 PyDict_SetItem(dmap, fname, entry) == -1)
197 197 goto quit;
198 198 }
199 199 cur += flen;
200 200 Py_DECREF(fname);
201 201 Py_DECREF(entry);
202 202 fname = cname = entry = NULL;
203 203 }
204 204
205 205 ret = parents;
206 206 Py_INCREF(ret);
207 207 quit:
208 208 Py_XDECREF(fname);
209 209 Py_XDECREF(cname);
210 210 Py_XDECREF(entry);
211 211 Py_XDECREF(parents);
212 212 return ret;
213 213 }
214 214
215 215 /*
216 216 * A base-16 trie for fast node->rev mapping.
217 217 *
218 218 * Positive value is index of the next node in the trie
219 219 * Negative value is a leaf: -(rev + 1)
220 220 * Zero is empty
221 221 */
222 222 typedef struct {
223 223 int children[16];
224 224 } nodetree;
225 225
226 226 /*
227 227 * This class has two behaviours.
228 228 *
229 229 * When used in a list-like way (with integer keys), we decode an
230 230 * entry in a RevlogNG index file on demand. Our last entry is a
231 231 * sentinel, always a nullid. We have limited support for
232 232 * integer-keyed insert and delete, only at elements right before the
233 233 * sentinel.
234 234 *
235 235 * With string keys, we lazily perform a reverse mapping from node to
236 236 * rev, using a base-16 trie.
237 237 */
238 238 typedef struct {
239 239 PyObject_HEAD
240 240 /* Type-specific fields go here. */
241 241 PyObject *data; /* raw bytes of index */
242 242 PyObject **cache; /* cached tuples */
243 243 const char **offsets; /* populated on demand */
244 244 Py_ssize_t raw_length; /* original number of elements */
245 245 Py_ssize_t length; /* current number of elements */
246 246 PyObject *added; /* populated on demand */
247 247 nodetree *nt; /* base-16 trie */
248 248 int ntlength; /* # nodes in use */
249 249 int ntcapacity; /* # nodes allocated */
250 250 int ntdepth; /* maximum depth of tree */
251 251 int ntsplits; /* # splits performed */
252 252 int ntrev; /* last rev scanned */
253 253 int ntlookups; /* # lookups */
254 254 int ntmisses; /* # lookups that miss the cache */
255 255 int inlined;
256 256 } indexObject;
257 257
258 258 static Py_ssize_t index_length(const indexObject *self)
259 259 {
260 260 if (self->added == NULL)
261 261 return self->length;
262 262 return self->length + PyList_GET_SIZE(self->added);
263 263 }
264 264
265 265 static PyObject *nullentry;
266 266 static const char nullid[20];
267 267
268 268 static long inline_scan(indexObject *self, const char **offsets);
269 269
270 270 #if LONG_MAX == 0x7fffffffL
271 271 static char *tuple_format = "Kiiiiiis#";
272 272 #else
273 273 static char *tuple_format = "kiiiiiis#";
274 274 #endif
275 275
276 276 /*
277 277 * Return a pointer to the beginning of a RevlogNG record.
278 278 */
279 279 static const char *index_deref(indexObject *self, Py_ssize_t pos)
280 280 {
281 281 if (self->inlined && pos > 0) {
282 282 if (self->offsets == NULL) {
283 283 self->offsets = malloc(self->raw_length *
284 284 sizeof(*self->offsets));
285 285 if (self->offsets == NULL)
286 286 return (const char *)PyErr_NoMemory();
287 287 inline_scan(self, self->offsets);
288 288 }
289 289 return self->offsets[pos];
290 290 }
291 291
292 292 return PyString_AS_STRING(self->data) + pos * 64;
293 293 }
294 294
295 295 /*
296 296 * RevlogNG format (all in big endian, data may be inlined):
297 297 * 6 bytes: offset
298 298 * 2 bytes: flags
299 299 * 4 bytes: compressed length
300 300 * 4 bytes: uncompressed length
301 301 * 4 bytes: base revision
302 302 * 4 bytes: link revision
303 303 * 4 bytes: parent 1 revision
304 304 * 4 bytes: parent 2 revision
305 305 * 32 bytes: nodeid (only 20 bytes used)
306 306 */
307 307 static PyObject *index_get(indexObject *self, Py_ssize_t pos)
308 308 {
309 309 uint64_t offset_flags;
310 310 int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
311 311 const char *c_node_id;
312 312 const char *data;
313 313 Py_ssize_t length = index_length(self);
314 314 PyObject *entry;
315 315
316 316 if (pos < 0)
317 317 pos += length;
318 318
319 319 if (pos < 0 || pos >= length) {
320 320 PyErr_SetString(PyExc_IndexError, "revlog index out of range");
321 321 return NULL;
322 322 }
323 323
324 324 if (pos == length - 1) {
325 325 Py_INCREF(nullentry);
326 326 return nullentry;
327 327 }
328 328
329 329 if (pos >= self->length - 1) {
330 330 PyObject *obj;
331 331 obj = PyList_GET_ITEM(self->added, pos - self->length + 1);
332 332 Py_INCREF(obj);
333 333 return obj;
334 334 }
335 335
336 336 if (self->cache) {
337 337 if (self->cache[pos]) {
338 338 Py_INCREF(self->cache[pos]);
339 339 return self->cache[pos];
340 340 }
341 341 } else {
342 342 self->cache = calloc(self->raw_length, sizeof(PyObject *));
343 343 if (self->cache == NULL)
344 344 return PyErr_NoMemory();
345 345 }
346 346
347 347 data = index_deref(self, pos);
348 348 if (data == NULL)
349 349 return NULL;
350 350
351 351 offset_flags = getbe32(data + 4);
352 352 if (pos == 0) /* mask out version number for the first entry */
353 353 offset_flags &= 0xFFFF;
354 354 else {
355 355 uint32_t offset_high = getbe32(data);
356 356 offset_flags |= ((uint64_t)offset_high) << 32;
357 357 }
358 358
359 359 comp_len = getbe32(data + 8);
360 360 uncomp_len = getbe32(data + 12);
361 361 base_rev = getbe32(data + 16);
362 362 link_rev = getbe32(data + 20);
363 363 parent_1 = getbe32(data + 24);
364 364 parent_2 = getbe32(data + 28);
365 365 c_node_id = data + 32;
366 366
367 367 entry = Py_BuildValue(tuple_format, offset_flags, comp_len,
368 368 uncomp_len, base_rev, link_rev,
369 369 parent_1, parent_2, c_node_id, 20);
370 370
371 371 if (entry)
372 372 PyObject_GC_UnTrack(entry);
373 373
374 374 self->cache[pos] = entry;
375 375 Py_INCREF(entry);
376 376
377 377 return entry;
378 378 }
379 379
380 380 /*
381 381 * Return the 20-byte SHA of the node corresponding to the given rev.
382 382 */
383 383 static const char *index_node(indexObject *self, Py_ssize_t pos)
384 384 {
385 385 Py_ssize_t length = index_length(self);
386 386 const char *data;
387 387
388 388 if (pos == length - 1)
389 389 return nullid;
390 390
391 391 if (pos >= length)
392 392 return NULL;
393 393
394 394 if (pos >= self->length - 1) {
395 395 PyObject *tuple, *str;
396 396 tuple = PyList_GET_ITEM(self->added, pos - self->length + 1);
397 397 str = PyTuple_GetItem(tuple, 7);
398 398 return str ? PyString_AS_STRING(str) : NULL;
399 399 }
400 400
401 401 data = index_deref(self, pos);
402 402 return data ? data + 32 : NULL;
403 403 }
404 404
405 405 static int nt_insert(indexObject *self, const char *node, int rev);
406 406
407 407 static int node_check(PyObject *obj, char **node, Py_ssize_t *nodelen)
408 408 {
409 409 if (PyString_AsStringAndSize(obj, node, nodelen) == -1)
410 410 return -1;
411 411 if (*nodelen == 20)
412 412 return 0;
413 413 PyErr_SetString(PyExc_ValueError, "20-byte hash required");
414 414 return -1;
415 415 }
416 416
417 417 static PyObject *index_insert(indexObject *self, PyObject *args)
418 418 {
419 419 PyObject *obj;
420 420 char *node;
421 421 long offset;
422 422 Py_ssize_t len, nodelen;
423 423
424 424 if (!PyArg_ParseTuple(args, "lO", &offset, &obj))
425 425 return NULL;
426 426
427 427 if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 8) {
428 428 PyErr_SetString(PyExc_TypeError, "8-tuple required");
429 429 return NULL;
430 430 }
431 431
432 432 if (node_check(PyTuple_GET_ITEM(obj, 7), &node, &nodelen) == -1)
433 433 return NULL;
434 434
435 435 len = index_length(self);
436 436
437 437 if (offset < 0)
438 438 offset += len;
439 439
440 440 if (offset != len - 1) {
441 441 PyErr_SetString(PyExc_IndexError,
442 442 "insert only supported at index -1");
443 443 return NULL;
444 444 }
445 445
446 446 if (offset > INT_MAX) {
447 447 PyErr_SetString(PyExc_ValueError,
448 448 "currently only 2**31 revs supported");
449 449 return NULL;
450 450 }
451 451
452 452 if (self->added == NULL) {
453 453 self->added = PyList_New(0);
454 454 if (self->added == NULL)
455 455 return NULL;
456 456 }
457 457
458 458 if (PyList_Append(self->added, obj) == -1)
459 459 return NULL;
460 460
461 461 if (self->nt)
462 462 nt_insert(self, node, (int)offset);
463 463
464 464 Py_RETURN_NONE;
465 465 }
466 466
467 467 static void _index_clearcaches(indexObject *self)
468 468 {
469 469 if (self->cache) {
470 470 Py_ssize_t i;
471 471
472 472 for (i = 0; i < self->raw_length; i++) {
473 473 if (self->cache[i]) {
474 474 Py_DECREF(self->cache[i]);
475 475 self->cache[i] = NULL;
476 476 }
477 477 }
478 478 free(self->cache);
479 479 self->cache = NULL;
480 480 }
481 481 if (self->offsets) {
482 482 free(self->offsets);
483 483 self->offsets = NULL;
484 484 }
485 485 if (self->nt) {
486 486 free(self->nt);
487 487 self->nt = NULL;
488 488 }
489 489 }
490 490
491 491 static PyObject *index_clearcaches(indexObject *self)
492 492 {
493 493 _index_clearcaches(self);
494 494 self->ntlength = self->ntcapacity = 0;
495 495 self->ntdepth = self->ntsplits = 0;
496 496 self->ntrev = -1;
497 497 self->ntlookups = self->ntmisses = 0;
498 498 Py_RETURN_NONE;
499 499 }
500 500
501 501 static PyObject *index_stats(indexObject *self)
502 502 {
503 503 PyObject *obj = PyDict_New();
504 504
505 505 if (obj == NULL)
506 506 return NULL;
507 507
508 508 #define istat(__n, __d) \
509 509 if (PyDict_SetItemString(obj, __d, PyInt_FromLong(self->__n)) == -1) \
510 510 goto bail;
511 511
512 512 if (self->added) {
513 513 Py_ssize_t len = PyList_GET_SIZE(self->added);
514 514 if (PyDict_SetItemString(obj, "index entries added",
515 515 PyInt_FromLong(len)) == -1)
516 516 goto bail;
517 517 }
518 518
519 519 if (self->raw_length != self->length - 1)
520 520 istat(raw_length, "revs on disk");
521 521 istat(length, "revs in memory");
522 522 istat(ntcapacity, "node trie capacity");
523 523 istat(ntdepth, "node trie depth");
524 524 istat(ntlength, "node trie count");
525 525 istat(ntlookups, "node trie lookups");
526 526 istat(ntmisses, "node trie misses");
527 527 istat(ntrev, "node trie last rev scanned");
528 528 istat(ntsplits, "node trie splits");
529 529
530 530 #undef istat
531 531
532 532 return obj;
533 533
534 534 bail:
535 535 Py_XDECREF(obj);
536 536 return NULL;
537 537 }
538 538
539 539 static inline int nt_level(const char *node, int level)
540 540 {
541 541 int v = node[level>>1];
542 542 if (!(level & 1))
543 543 v >>= 4;
544 544 return v & 0xf;
545 545 }
546 546
547 547 static int nt_find(indexObject *self, const char *node, Py_ssize_t nodelen)
548 548 {
549 549 int level, off;
550 550
551 551 if (nodelen == 20 && node[0] == '\0' && memcmp(node, nullid, 20) == 0)
552 552 return -1;
553 553
554 554 if (self->nt == NULL)
555 555 return -2;
556 556
557 557 for (level = off = 0; level < nodelen; level++) {
558 558 int k = nt_level(node, level);
559 559 nodetree *n = &self->nt[off];
560 560 int v = n->children[k];
561 561
562 562 if (v < 0) {
563 563 const char *n;
564 564 v = -v - 1;
565 565 n = index_node(self, v);
566 566 if (n == NULL)
567 567 return -2;
568 568 return memcmp(node, n, nodelen > 20 ? 20 : nodelen)
569 569 ? -2 : v;
570 570 }
571 571 if (v == 0)
572 572 return -2;
573 573 off = v;
574 574 }
575 575 return -2;
576 576 }
577 577
578 578 static int nt_new(indexObject *self)
579 579 {
580 580 if (self->ntlength == self->ntcapacity) {
581 581 self->ntcapacity *= 2;
582 582 self->nt = realloc(self->nt,
583 583 self->ntcapacity * sizeof(nodetree));
584 584 if (self->nt == NULL) {
585 585 PyErr_SetString(PyExc_MemoryError, "out of memory");
586 586 return -1;
587 587 }
588 588 memset(&self->nt[self->ntlength], 0,
589 589 sizeof(nodetree) * (self->ntcapacity - self->ntlength));
590 590 }
591 591 return self->ntlength++;
592 592 }
593 593
594 594 static int nt_insert(indexObject *self, const char *node, int rev)
595 595 {
596 596 int level = 0;
597 597 int off = 0;
598 598
599 599 while (level < 20) {
600 600 int k = nt_level(node, level);
601 601 nodetree *n;
602 602 int v;
603 603
604 604 n = &self->nt[off];
605 605 v = n->children[k];
606 606
607 607 if (v == 0) {
608 608 n->children[k] = -rev - 1;
609 609 return 0;
610 610 }
611 611 if (v < 0) {
612 612 const char *oldnode = index_node(self, -v - 1);
613 613 int noff;
614 614
615 615 if (!oldnode || !memcmp(oldnode, node, 20)) {
616 616 n->children[k] = -rev - 1;
617 617 return 0;
618 618 }
619 619 noff = nt_new(self);
620 620 if (noff == -1)
621 621 return -1;
622 622 /* self->nt may have been changed by realloc */
623 623 self->nt[off].children[k] = noff;
624 624 off = noff;
625 625 n = &self->nt[off];
626 626 n->children[nt_level(oldnode, ++level)] = v;
627 627 if (level > self->ntdepth)
628 628 self->ntdepth = level;
629 629 self->ntsplits += 1;
630 630 } else {
631 631 level += 1;
632 632 off = v;
633 633 }
634 634 }
635 635
636 636 return -1;
637 637 }
638 638
639 639 /*
640 640 * Return values:
641 641 *
642 642 * -3: error (exception set)
643 643 * -2: not found (no exception set)
644 644 * rest: valid rev
645 645 */
646 646 static int index_find_node(indexObject *self,
647 647 const char *node, Py_ssize_t nodelen)
648 648 {
649 649 int rev;
650 650
651 651 self->ntlookups++;
652 652 rev = nt_find(self, node, nodelen);
653 653 if (rev >= -1)
654 654 return rev;
655 655
656 656 if (self->nt == NULL) {
657 657 self->ntcapacity = self->raw_length < 4
658 658 ? 4 : self->raw_length / 2;
659 659 self->nt = calloc(self->ntcapacity, sizeof(nodetree));
660 660 if (self->nt == NULL) {
661 661 PyErr_SetString(PyExc_MemoryError, "out of memory");
662 662 return -3;
663 663 }
664 664 self->ntlength = 1;
665 665 self->ntrev = (int)index_length(self) - 1;
666 666 self->ntlookups = 1;
667 667 self->ntmisses = 0;
668 668 }
669 669
670 670 /*
671 671 * For the first handful of lookups, we scan the entire index,
672 672 * and cache only the matching nodes. This optimizes for cases
673 673 * like "hg tip", where only a few nodes are accessed.
674 674 *
675 675 * After that, we cache every node we visit, using a single
676 676 * scan amortized over multiple lookups. This gives the best
677 677 * bulk performance, e.g. for "hg log".
678 678 */
679 679 if (self->ntmisses++ < 4) {
680 680 for (rev = self->ntrev - 1; rev >= 0; rev--) {
681 681 const char *n = index_node(self, rev);
682 682 if (n == NULL)
683 683 return -2;
684 684 if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
685 685 if (nt_insert(self, n, rev) == -1)
686 686 return -3;
687 687 break;
688 688 }
689 689 }
690 690 } else {
691 691 for (rev = self->ntrev - 1; rev >= 0; rev--) {
692 692 const char *n = index_node(self, rev);
693 693 if (n == NULL)
694 694 return -2;
695 695 if (nt_insert(self, n, rev) == -1)
696 696 return -3;
697 697 if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
698 698 break;
699 699 }
700 700 }
701 701 self->ntrev = rev;
702 702 }
703 703
704 704 if (rev >= 0)
705 705 return rev;
706 706 return -2;
707 707 }
708 708
709 709 static PyObject *raise_revlog_error(void)
710 710 {
711 711 static PyObject *errclass;
712 712 PyObject *mod = NULL, *errobj;
713 713
714 714 if (errclass == NULL) {
715 715 PyObject *dict;
716 716
717 717 mod = PyImport_ImportModule("mercurial.error");
718 718 if (mod == NULL)
719 719 goto classfail;
720 720
721 721 dict = PyModule_GetDict(mod);
722 722 if (dict == NULL)
723 723 goto classfail;
724 724
725 725 errclass = PyDict_GetItemString(dict, "RevlogError");
726 726 if (errclass == NULL) {
727 727 PyErr_SetString(PyExc_SystemError,
728 728 "could not find RevlogError");
729 729 goto classfail;
730 730 }
731 731 Py_INCREF(errclass);
732 732 }
733 733
734 734 errobj = PyObject_CallFunction(errclass, NULL);
735 735 if (errobj == NULL)
736 736 return NULL;
737 737 PyErr_SetObject(errclass, errobj);
738 738 return errobj;
739 739
740 740 classfail:
741 741 Py_XDECREF(mod);
742 742 return NULL;
743 743 }
744 744
745 745 static PyObject *index_getitem(indexObject *self, PyObject *value)
746 746 {
747 747 char *node;
748 748 Py_ssize_t nodelen;
749 749 int rev;
750 750
751 751 if (PyInt_Check(value))
752 752 return index_get(self, PyInt_AS_LONG(value));
753 753
754 754 if (PyString_AsStringAndSize(value, &node, &nodelen) == -1)
755 755 return NULL;
756 756 rev = index_find_node(self, node, nodelen);
757 757 if (rev >= -1)
758 758 return PyInt_FromLong(rev);
759 759 if (rev == -2)
760 760 raise_revlog_error();
761 761 return NULL;
762 762 }
763 763
764 764 static PyObject *index_m_get(indexObject *self, PyObject *args)
765 765 {
766 766 char *node;
767 767 int nodelen, rev;
768 768
769 769 if (!PyArg_ParseTuple(args, "s#", &node, &nodelen))
770 770 return NULL;
771 771
772 772 rev = index_find_node(self, node, nodelen);
773 773 if (rev == -3)
774 774 return NULL;
775 775 if (rev == -2)
776 776 Py_RETURN_NONE;
777 777 return PyInt_FromLong(rev);
778 778 }
779 779
780 780 static int index_contains(indexObject *self, PyObject *value)
781 781 {
782 782 char *node;
783 783 Py_ssize_t nodelen;
784 784
785 785 if (PyInt_Check(value)) {
786 786 long rev = PyInt_AS_LONG(value);
787 787 return rev >= -1 && rev < index_length(self);
788 788 }
789 789
790 790 if (!PyString_Check(value))
791 791 return 0;
792 792
793 793 node = PyString_AS_STRING(value);
794 794 nodelen = PyString_GET_SIZE(value);
795 795
796 796 switch (index_find_node(self, node, nodelen)) {
797 797 case -3:
798 798 return -1;
799 799 case -2:
800 800 return 0;
801 801 default:
802 802 return 1;
803 803 }
804 804 }
805 805
806 806 /*
807 807 * Invalidate any trie entries introduced by added revs.
808 808 */
809 809 static void nt_invalidate_added(indexObject *self, Py_ssize_t start)
810 810 {
811 811 Py_ssize_t i, len = PyList_GET_SIZE(self->added);
812 812
813 813 for (i = start; i < len; i++) {
814 814 PyObject *tuple = PyList_GET_ITEM(self->added, i);
815 815 PyObject *node = PyTuple_GET_ITEM(tuple, 7);
816 816
817 817 nt_insert(self, PyString_AS_STRING(node), -1);
818 818 }
819 819
820 820 if (start == 0) {
821 821 Py_DECREF(self->added);
822 822 self->added = NULL;
823 823 }
824 824 }
825 825
826 826 /*
827 827 * Delete a numeric range of revs, which must be at the end of the
828 828 * range, but exclude the sentinel nullid entry.
829 829 */
830 830 static int index_slice_del(indexObject *self, PyObject *item)
831 831 {
832 832 Py_ssize_t start, stop, step, slicelength;
833 833 Py_ssize_t length = index_length(self);
834 834
835 835 if (PySlice_GetIndicesEx((PySliceObject*)item, length,
836 836 &start, &stop, &step, &slicelength) < 0)
837 837 return -1;
838 838
839 839 if (slicelength <= 0)
840 840 return 0;
841 841
842 842 if ((step < 0 && start < stop) || (step > 0 && start > stop))
843 843 stop = start;
844 844
845 845 if (step < 0) {
846 846 stop = start + 1;
847 847 start = stop + step*(slicelength - 1) - 1;
848 848 step = -step;
849 849 }
850 850
851 851 if (step != 1) {
852 852 PyErr_SetString(PyExc_ValueError,
853 853 "revlog index delete requires step size of 1");
854 854 return -1;
855 855 }
856 856
857 857 if (stop != length - 1) {
858 858 PyErr_SetString(PyExc_IndexError,
859 859 "revlog index deletion indices are invalid");
860 860 return -1;
861 861 }
862 862
863 863 if (start < self->length - 1) {
864 864 if (self->nt) {
865 865 Py_ssize_t i;
866 866
867 867 for (i = start + 1; i < self->length - 1; i++) {
868 868 const char *node = index_node(self, i);
869 869
870 870 if (node)
871 871 nt_insert(self, node, -1);
872 872 }
873 873 if (self->added)
874 874 nt_invalidate_added(self, 0);
875 875 if (self->ntrev > start)
876 876 self->ntrev = (int)start;
877 877 }
878 878 self->length = start + 1;
879 879 return 0;
880 880 }
881 881
882 882 if (self->nt) {
883 883 nt_invalidate_added(self, start - self->length + 1);
884 884 if (self->ntrev > start)
885 885 self->ntrev = (int)start;
886 886 }
887 887 return self->added
888 888 ? PyList_SetSlice(self->added, start - self->length + 1,
889 889 PyList_GET_SIZE(self->added), NULL)
890 890 : 0;
891 891 }
892 892
893 893 /*
894 894 * Supported ops:
895 895 *
896 896 * slice deletion
897 897 * string assignment (extend node->rev mapping)
898 898 * string deletion (shrink node->rev mapping)
899 899 */
900 900 static int index_assign_subscript(indexObject *self, PyObject *item,
901 901 PyObject *value)
902 902 {
903 903 char *node;
904 904 Py_ssize_t nodelen;
905 905 long rev;
906 906
907 907 if (PySlice_Check(item) && value == NULL)
908 908 return index_slice_del(self, item);
909 909
910 910 if (node_check(item, &node, &nodelen) == -1)
911 911 return -1;
912 912
913 913 if (value == NULL)
914 914 return self->nt ? nt_insert(self, node, -1) : 0;
915 915 rev = PyInt_AsLong(value);
916 916 if (rev > INT_MAX || rev < 0) {
917 917 if (!PyErr_Occurred())
918 918 PyErr_SetString(PyExc_ValueError, "rev out of range");
919 919 return -1;
920 920 }
921 921 return nt_insert(self, node, (int)rev);
922 922 }
923 923
924 924 /*
925 925 * Find all RevlogNG entries in an index that has inline data. Update
926 926 * the optional "offsets" table with those entries.
927 927 */
928 928 static long inline_scan(indexObject *self, const char **offsets)
929 929 {
930 930 const char *data = PyString_AS_STRING(self->data);
931 931 const char *end = data + PyString_GET_SIZE(self->data);
932 932 const long hdrsize = 64;
933 933 long incr = hdrsize;
934 934 Py_ssize_t len = 0;
935 935
936 936 while (data + hdrsize <= end) {
937 937 uint32_t comp_len;
938 938 const char *old_data;
939 939 /* 3rd element of header is length of compressed inline data */
940 940 comp_len = getbe32(data + 8);
941 941 incr = hdrsize + comp_len;
942 942 if (incr < hdrsize)
943 943 break;
944 944 if (offsets)
945 945 offsets[len] = data;
946 946 len++;
947 947 old_data = data;
948 948 data += incr;
949 949 if (data <= old_data)
950 950 break;
951 951 }
952 952
953 953 if (data != end && data + hdrsize != end) {
954 954 if (!PyErr_Occurred())
955 955 PyErr_SetString(PyExc_ValueError, "corrupt index file");
956 956 return -1;
957 957 }
958 958
959 959 return len;
960 960 }
961 961
962 962 static int index_init(indexObject *self, PyObject *args)
963 963 {
964 964 PyObject *data_obj, *inlined_obj;
965 965 Py_ssize_t size;
966 966
967 967 if (!PyArg_ParseTuple(args, "OO", &data_obj, &inlined_obj))
968 968 return -1;
969 969 if (!PyString_Check(data_obj)) {
970 970 PyErr_SetString(PyExc_TypeError, "data is not a string");
971 971 return -1;
972 972 }
973 973 size = PyString_GET_SIZE(data_obj);
974 974
975 975 self->inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
976 976 self->data = data_obj;
977 977 self->cache = NULL;
978 978
979 979 self->added = NULL;
980 980 self->offsets = NULL;
981 981 self->nt = NULL;
982 982 self->ntlength = self->ntcapacity = 0;
983 983 self->ntdepth = self->ntsplits = 0;
984 984 self->ntlookups = self->ntmisses = 0;
985 985 self->ntrev = -1;
986 Py_INCREF(self->data);
986 987
987 988 if (self->inlined) {
988 989 long len = inline_scan(self, NULL);
989 990 if (len == -1)
990 991 goto bail;
991 992 self->raw_length = len;
992 993 self->length = len + 1;
993 994 } else {
994 995 if (size % 64) {
995 996 PyErr_SetString(PyExc_ValueError, "corrupt index file");
996 997 goto bail;
997 998 }
998 999 self->raw_length = size / 64;
999 1000 self->length = self->raw_length + 1;
1000 1001 }
1001 Py_INCREF(self->data);
1002 1002
1003 1003 return 0;
1004 1004 bail:
1005 1005 return -1;
1006 1006 }
1007 1007
1008 1008 static PyObject *index_nodemap(indexObject *self)
1009 1009 {
1010 1010 Py_INCREF(self);
1011 1011 return (PyObject *)self;
1012 1012 }
1013 1013
1014 1014 static void index_dealloc(indexObject *self)
1015 1015 {
1016 1016 _index_clearcaches(self);
1017 1017 Py_DECREF(self->data);
1018 1018 Py_XDECREF(self->added);
1019 1019 PyObject_Del(self);
1020 1020 }
1021 1021
1022 1022 static PySequenceMethods index_sequence_methods = {
1023 1023 (lenfunc)index_length, /* sq_length */
1024 1024 0, /* sq_concat */
1025 1025 0, /* sq_repeat */
1026 1026 (ssizeargfunc)index_get, /* sq_item */
1027 1027 0, /* sq_slice */
1028 1028 0, /* sq_ass_item */
1029 1029 0, /* sq_ass_slice */
1030 1030 (objobjproc)index_contains, /* sq_contains */
1031 1031 };
1032 1032
1033 1033 static PyMappingMethods index_mapping_methods = {
1034 1034 (lenfunc)index_length, /* mp_length */
1035 1035 (binaryfunc)index_getitem, /* mp_subscript */
1036 1036 (objobjargproc)index_assign_subscript, /* mp_ass_subscript */
1037 1037 };
1038 1038
1039 1039 static PyMethodDef index_methods[] = {
1040 1040 {"clearcaches", (PyCFunction)index_clearcaches, METH_NOARGS,
1041 1041 "clear the index caches"},
1042 1042 {"get", (PyCFunction)index_m_get, METH_VARARGS,
1043 1043 "get an index entry"},
1044 1044 {"insert", (PyCFunction)index_insert, METH_VARARGS,
1045 1045 "insert an index entry"},
1046 1046 {"stats", (PyCFunction)index_stats, METH_NOARGS,
1047 1047 "stats for the index"},
1048 1048 {NULL} /* Sentinel */
1049 1049 };
1050 1050
1051 1051 static PyGetSetDef index_getset[] = {
1052 1052 {"nodemap", (getter)index_nodemap, NULL, "nodemap", NULL},
1053 1053 {NULL} /* Sentinel */
1054 1054 };
1055 1055
1056 1056 static PyTypeObject indexType = {
1057 1057 PyObject_HEAD_INIT(NULL)
1058 1058 0, /* ob_size */
1059 1059 "parsers.index", /* tp_name */
1060 1060 sizeof(indexObject), /* tp_basicsize */
1061 1061 0, /* tp_itemsize */
1062 1062 (destructor)index_dealloc, /* tp_dealloc */
1063 1063 0, /* tp_print */
1064 1064 0, /* tp_getattr */
1065 1065 0, /* tp_setattr */
1066 1066 0, /* tp_compare */
1067 1067 0, /* tp_repr */
1068 1068 0, /* tp_as_number */
1069 1069 &index_sequence_methods, /* tp_as_sequence */
1070 1070 &index_mapping_methods, /* tp_as_mapping */
1071 1071 0, /* tp_hash */
1072 1072 0, /* tp_call */
1073 1073 0, /* tp_str */
1074 1074 0, /* tp_getattro */
1075 1075 0, /* tp_setattro */
1076 1076 0, /* tp_as_buffer */
1077 1077 Py_TPFLAGS_DEFAULT, /* tp_flags */
1078 1078 "revlog index", /* tp_doc */
1079 1079 0, /* tp_traverse */
1080 1080 0, /* tp_clear */
1081 1081 0, /* tp_richcompare */
1082 1082 0, /* tp_weaklistoffset */
1083 1083 0, /* tp_iter */
1084 1084 0, /* tp_iternext */
1085 1085 index_methods, /* tp_methods */
1086 1086 0, /* tp_members */
1087 1087 index_getset, /* tp_getset */
1088 1088 0, /* tp_base */
1089 1089 0, /* tp_dict */
1090 1090 0, /* tp_descr_get */
1091 1091 0, /* tp_descr_set */
1092 1092 0, /* tp_dictoffset */
1093 1093 (initproc)index_init, /* tp_init */
1094 1094 0, /* tp_alloc */
1095 1095 PyType_GenericNew, /* tp_new */
1096 1096 };
1097 1097
1098 1098 /*
1099 1099 * returns a tuple of the form (index, index, cache) with elements as
1100 1100 * follows:
1101 1101 *
1102 1102 * index: an index object that lazily parses RevlogNG records
1103 1103 * cache: if data is inlined, a tuple (index_file_content, 0), else None
1104 1104 *
1105 1105 * added complications are for backwards compatibility
1106 1106 */
1107 1107 static PyObject *parse_index2(PyObject *self, PyObject *args)
1108 1108 {
1109 1109 PyObject *tuple = NULL, *cache = NULL;
1110 1110 indexObject *idx;
1111 1111 int ret;
1112 1112
1113 1113 idx = PyObject_New(indexObject, &indexType);
1114 1114 if (idx == NULL)
1115 1115 goto bail;
1116 1116
1117 1117 ret = index_init(idx, args);
1118 1118 if (ret == -1)
1119 1119 goto bail;
1120 1120
1121 1121 if (idx->inlined) {
1122 1122 cache = Py_BuildValue("iO", 0, idx->data);
1123 1123 if (cache == NULL)
1124 1124 goto bail;
1125 1125 } else {
1126 1126 cache = Py_None;
1127 1127 Py_INCREF(cache);
1128 1128 }
1129 1129
1130 1130 tuple = Py_BuildValue("NN", idx, cache);
1131 1131 if (!tuple)
1132 1132 goto bail;
1133 1133 return tuple;
1134 1134
1135 1135 bail:
1136 1136 Py_XDECREF(idx);
1137 1137 Py_XDECREF(cache);
1138 1138 Py_XDECREF(tuple);
1139 1139 return NULL;
1140 1140 }
1141 1141
1142 1142 static char parsers_doc[] = "Efficient content parsing.";
1143 1143
1144 1144 static PyMethodDef methods[] = {
1145 1145 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
1146 1146 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1147 1147 {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
1148 1148 {NULL, NULL}
1149 1149 };
1150 1150
1151 1151 static void module_init(PyObject *mod)
1152 1152 {
1153 1153 if (PyType_Ready(&indexType) < 0)
1154 1154 return;
1155 1155 Py_INCREF(&indexType);
1156 1156
1157 1157 PyModule_AddObject(mod, "index", (PyObject *)&indexType);
1158 1158
1159 1159 nullentry = Py_BuildValue("iiiiiiis#", 0, 0, 0,
1160 1160 -1, -1, -1, -1, nullid, 20);
1161 1161 if (nullentry)
1162 1162 PyObject_GC_UnTrack(nullentry);
1163 1163 }
1164 1164
1165 1165 #ifdef IS_PY3K
1166 1166 static struct PyModuleDef parsers_module = {
1167 1167 PyModuleDef_HEAD_INIT,
1168 1168 "parsers",
1169 1169 parsers_doc,
1170 1170 -1,
1171 1171 methods
1172 1172 };
1173 1173
1174 1174 PyMODINIT_FUNC PyInit_parsers(void)
1175 1175 {
1176 1176 PyObject *mod = PyModule_Create(&parsers_module);
1177 1177 module_init(mod);
1178 1178 return mod;
1179 1179 }
1180 1180 #else
1181 1181 PyMODINIT_FUNC initparsers(void)
1182 1182 {
1183 1183 PyObject *mod = Py_InitModule3("parsers", methods, parsers_doc);
1184 1184 module_init(mod);
1185 1185 }
1186 1186 #endif
@@ -1,74 +1,75 b''
1 1 # base85.py: pure python base85 codec
2 2 #
3 3 # Copyright (C) 2009 Brendan Cully <brendan@kublai.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 struct
9 9
10 10 _b85chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
11 11 "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
12 12 _b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
13 13 _b85dec = {}
14 14
15 15 def _mkb85dec():
16 16 for i, c in enumerate(_b85chars):
17 17 _b85dec[c] = i
18 18
19 19 def b85encode(text, pad=False):
20 20 """encode text in base85 format"""
21 21 l = len(text)
22 22 r = l % 4
23 23 if r:
24 24 text += '\0' * (4 - r)
25 25 longs = len(text) >> 2
26 26 words = struct.unpack('>%dL' % (longs), text)
27 27
28 28 out = ''.join(_b85chars[(word // 52200625) % 85] +
29 29 _b85chars2[(word // 7225) % 7225] +
30 30 _b85chars2[word % 7225]
31 31 for word in words)
32 32
33 33 if pad:
34 34 return out
35 35
36 36 # Trim padding
37 37 olen = l % 4
38 38 if olen:
39 39 olen += 1
40 40 olen += l // 4 * 5
41 41 return out[:olen]
42 42
43 43 def b85decode(text):
44 44 """decode base85-encoded text"""
45 45 if not _b85dec:
46 46 _mkb85dec()
47 47
48 48 l = len(text)
49 49 out = []
50 50 for i in range(0, len(text), 5):
51 51 chunk = text[i:i + 5]
52 52 acc = 0
53 53 for j, c in enumerate(chunk):
54 54 try:
55 55 acc = acc * 85 + _b85dec[c]
56 56 except KeyError:
57 raise TypeError('Bad base85 character at byte %d' % (i + j))
57 raise ValueError('bad base85 character at position %d'
58 % (i + j))
58 59 if acc > 4294967295:
59 raise OverflowError('Base85 overflow in hunk starting at byte %d' % i)
60 raise ValueError('Base85 overflow in hunk starting at byte %d' % i)
60 61 out.append(acc)
61 62
62 63 # Pad final chunk if necessary
63 64 cl = l % 5
64 65 if cl:
65 66 acc *= 85 ** (5 - cl)
66 67 if cl > 1:
67 68 acc += 0xffffff >> (cl - 2) * 8
68 69 out[-1] = acc
69 70
70 71 out = struct.pack('>%dL' % (len(out)), *out)
71 72 if cl:
72 73 out = out[:-(5 - cl)]
73 74
74 75 return out
@@ -1,1234 +1,1242 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 for l in ctx['.hgsubstate'].data().splitlines():
47 revision, path = l.split(" ", 1)
46 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
47 l = l.lstrip()
48 if not l:
49 continue
50 try:
51 revision, path = l.split(" ", 1)
52 except ValueError:
53 raise util.Abort(_("invalid subrepository revision "
54 "specifier in .hgsubstate line %d")
55 % (i + 1))
48 56 rev[path] = revision
49 57 except IOError, err:
50 58 if err.errno != errno.ENOENT:
51 59 raise
52 60
53 61 def remap(src):
54 62 for pattern, repl in p.items('subpaths'):
55 63 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
56 64 # does a string decode.
57 65 repl = repl.encode('string-escape')
58 66 # However, we still want to allow back references to go
59 67 # through unharmed, so we turn r'\\1' into r'\1'. Again,
60 68 # extra escapes are needed because re.sub string decodes.
61 69 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
62 70 try:
63 71 src = re.sub(pattern, repl, src, 1)
64 72 except re.error, e:
65 73 raise util.Abort(_("bad subrepository pattern in %s: %s")
66 74 % (p.source('subpaths', pattern), e))
67 75 return src
68 76
69 77 state = {}
70 78 for path, src in p[''].items():
71 79 kind = 'hg'
72 80 if src.startswith('['):
73 81 if ']' not in src:
74 82 raise util.Abort(_('missing ] in subrepo source'))
75 83 kind, src = src.split(']', 1)
76 84 kind = kind[1:]
77 85 src = src.lstrip() # strip any extra whitespace after ']'
78 86
79 87 if not util.url(src).isabs():
80 88 parent = _abssource(ctx._repo, abort=False)
81 89 if parent:
82 90 parent = util.url(parent)
83 91 parent.path = posixpath.join(parent.path or '', src)
84 92 parent.path = posixpath.normpath(parent.path)
85 93 joined = str(parent)
86 94 # Remap the full joined path and use it if it changes,
87 95 # else remap the original source.
88 96 remapped = remap(joined)
89 97 if remapped == joined:
90 98 src = remap(src)
91 99 else:
92 100 src = remapped
93 101
94 102 src = remap(src)
95 103 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
96 104
97 105 return state
98 106
99 107 def writestate(repo, state):
100 108 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
101 109 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
102 110 repo.wwrite('.hgsubstate', ''.join(lines), '')
103 111
104 112 def submerge(repo, wctx, mctx, actx, overwrite):
105 113 """delegated from merge.applyupdates: merging of .hgsubstate file
106 114 in working context, merging context and ancestor context"""
107 115 if mctx == actx: # backwards?
108 116 actx = wctx.p1()
109 117 s1 = wctx.substate
110 118 s2 = mctx.substate
111 119 sa = actx.substate
112 120 sm = {}
113 121
114 122 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
115 123
116 124 def debug(s, msg, r=""):
117 125 if r:
118 126 r = "%s:%s:%s" % r
119 127 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
120 128
121 129 for s, l in s1.items():
122 130 a = sa.get(s, nullstate)
123 131 ld = l # local state with possible dirty flag for compares
124 132 if wctx.sub(s).dirty():
125 133 ld = (l[0], l[1] + "+")
126 134 if wctx == actx: # overwrite
127 135 a = ld
128 136
129 137 if s in s2:
130 138 r = s2[s]
131 139 if ld == r or r == a: # no change or local is newer
132 140 sm[s] = l
133 141 continue
134 142 elif ld == a: # other side changed
135 143 debug(s, "other changed, get", r)
136 144 wctx.sub(s).get(r, overwrite)
137 145 sm[s] = r
138 146 elif ld[0] != r[0]: # sources differ
139 147 if repo.ui.promptchoice(
140 148 _(' subrepository sources for %s differ\n'
141 149 'use (l)ocal source (%s) or (r)emote source (%s)?')
142 150 % (s, l[0], r[0]),
143 151 (_('&Local'), _('&Remote')), 0):
144 152 debug(s, "prompt changed, get", r)
145 153 wctx.sub(s).get(r, overwrite)
146 154 sm[s] = r
147 155 elif ld[1] == a[1]: # local side is unchanged
148 156 debug(s, "other side changed, get", r)
149 157 wctx.sub(s).get(r, overwrite)
150 158 sm[s] = r
151 159 else:
152 160 debug(s, "both sides changed, merge with", r)
153 161 wctx.sub(s).merge(r)
154 162 sm[s] = l
155 163 elif ld == a: # remote removed, local unchanged
156 164 debug(s, "remote removed, remove")
157 165 wctx.sub(s).remove()
158 166 elif a == nullstate: # not present in remote or ancestor
159 167 debug(s, "local added, keep")
160 168 sm[s] = l
161 169 continue
162 170 else:
163 171 if repo.ui.promptchoice(
164 172 _(' local changed subrepository %s which remote removed\n'
165 173 'use (c)hanged version or (d)elete?') % s,
166 174 (_('&Changed'), _('&Delete')), 0):
167 175 debug(s, "prompt remove")
168 176 wctx.sub(s).remove()
169 177
170 178 for s, r in sorted(s2.items()):
171 179 if s in s1:
172 180 continue
173 181 elif s not in sa:
174 182 debug(s, "remote added, get", r)
175 183 mctx.sub(s).get(r)
176 184 sm[s] = r
177 185 elif r != sa[s]:
178 186 if repo.ui.promptchoice(
179 187 _(' remote changed subrepository %s which local removed\n'
180 188 'use (c)hanged version or (d)elete?') % s,
181 189 (_('&Changed'), _('&Delete')), 0) == 0:
182 190 debug(s, "prompt recreate", r)
183 191 wctx.sub(s).get(r)
184 192 sm[s] = r
185 193
186 194 # record merged .hgsubstate
187 195 writestate(repo, sm)
188 196
189 197 def _updateprompt(ui, sub, dirty, local, remote):
190 198 if dirty:
191 199 msg = (_(' subrepository sources for %s differ\n'
192 200 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
193 201 % (subrelpath(sub), local, remote))
194 202 else:
195 203 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
196 204 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
197 205 % (subrelpath(sub), local, remote))
198 206 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
199 207
200 208 def reporelpath(repo):
201 209 """return path to this (sub)repo as seen from outermost repo"""
202 210 parent = repo
203 211 while util.safehasattr(parent, '_subparent'):
204 212 parent = parent._subparent
205 213 p = parent.root.rstrip(os.sep)
206 214 return repo.root[len(p) + 1:]
207 215
208 216 def subrelpath(sub):
209 217 """return path to this subrepo as seen from outermost repo"""
210 218 if util.safehasattr(sub, '_relpath'):
211 219 return sub._relpath
212 220 if not util.safehasattr(sub, '_repo'):
213 221 return sub._path
214 222 return reporelpath(sub._repo)
215 223
216 224 def _abssource(repo, push=False, abort=True):
217 225 """return pull/push path of repo - either based on parent repo .hgsub info
218 226 or on the top repo config. Abort or return None if no source found."""
219 227 if util.safehasattr(repo, '_subparent'):
220 228 source = util.url(repo._subsource)
221 229 if source.isabs():
222 230 return str(source)
223 231 source.path = posixpath.normpath(source.path)
224 232 parent = _abssource(repo._subparent, push, abort=False)
225 233 if parent:
226 234 parent = util.url(util.pconvert(parent))
227 235 parent.path = posixpath.join(parent.path or '', source.path)
228 236 parent.path = posixpath.normpath(parent.path)
229 237 return str(parent)
230 238 else: # recursion reached top repo
231 239 if util.safehasattr(repo, '_subtoppath'):
232 240 return repo._subtoppath
233 241 if push and repo.ui.config('paths', 'default-push'):
234 242 return repo.ui.config('paths', 'default-push')
235 243 if repo.ui.config('paths', 'default'):
236 244 return repo.ui.config('paths', 'default')
237 245 if abort:
238 246 raise util.Abort(_("default path for subrepository %s not found") %
239 247 reporelpath(repo))
240 248
241 249 def itersubrepos(ctx1, ctx2):
242 250 """find subrepos in ctx1 or ctx2"""
243 251 # Create a (subpath, ctx) mapping where we prefer subpaths from
244 252 # ctx1. The subpaths from ctx2 are important when the .hgsub file
245 253 # has been modified (in ctx2) but not yet committed (in ctx1).
246 254 subpaths = dict.fromkeys(ctx2.substate, ctx2)
247 255 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
248 256 for subpath, ctx in sorted(subpaths.iteritems()):
249 257 yield subpath, ctx.sub(subpath)
250 258
251 259 def subrepo(ctx, path):
252 260 """return instance of the right subrepo class for subrepo in path"""
253 261 # subrepo inherently violates our import layering rules
254 262 # because it wants to make repo objects from deep inside the stack
255 263 # so we manually delay the circular imports to not break
256 264 # scripts that don't use our demand-loading
257 265 global hg
258 266 import hg as h
259 267 hg = h
260 268
261 269 scmutil.pathauditor(ctx._repo.root)(path)
262 270 state = ctx.substate.get(path, nullstate)
263 271 if state[2] not in types:
264 272 raise util.Abort(_('unknown subrepo type %s') % state[2])
265 273 return types[state[2]](ctx, path, state[:2])
266 274
267 275 # subrepo classes need to implement the following abstract class:
268 276
269 277 class abstractsubrepo(object):
270 278
271 279 def dirty(self, ignoreupdate=False):
272 280 """returns true if the dirstate of the subrepo is dirty or does not
273 281 match current stored state. If ignoreupdate is true, only check
274 282 whether the subrepo has uncommitted changes in its dirstate.
275 283 """
276 284 raise NotImplementedError
277 285
278 286 def basestate(self):
279 287 """current working directory base state, disregarding .hgsubstate
280 288 state and working directory modifications"""
281 289 raise NotImplementedError
282 290
283 291 def checknested(self, path):
284 292 """check if path is a subrepository within this repository"""
285 293 return False
286 294
287 295 def commit(self, text, user, date):
288 296 """commit the current changes to the subrepo with the given
289 297 log message. Use given user and date if possible. Return the
290 298 new state of the subrepo.
291 299 """
292 300 raise NotImplementedError
293 301
294 302 def remove(self):
295 303 """remove the subrepo
296 304
297 305 (should verify the dirstate is not dirty first)
298 306 """
299 307 raise NotImplementedError
300 308
301 309 def get(self, state, overwrite=False):
302 310 """run whatever commands are needed to put the subrepo into
303 311 this state
304 312 """
305 313 raise NotImplementedError
306 314
307 315 def merge(self, state):
308 316 """merge currently-saved state with the new state."""
309 317 raise NotImplementedError
310 318
311 319 def push(self, opts):
312 320 """perform whatever action is analogous to 'hg push'
313 321
314 322 This may be a no-op on some systems.
315 323 """
316 324 raise NotImplementedError
317 325
318 326 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
319 327 return []
320 328
321 329 def status(self, rev2, **opts):
322 330 return [], [], [], [], [], [], []
323 331
324 332 def diff(self, diffopts, node2, match, prefix, **opts):
325 333 pass
326 334
327 335 def outgoing(self, ui, dest, opts):
328 336 return 1
329 337
330 338 def incoming(self, ui, source, opts):
331 339 return 1
332 340
333 341 def files(self):
334 342 """return filename iterator"""
335 343 raise NotImplementedError
336 344
337 345 def filedata(self, name):
338 346 """return file data"""
339 347 raise NotImplementedError
340 348
341 349 def fileflags(self, name):
342 350 """return file flags"""
343 351 return ''
344 352
345 353 def archive(self, ui, archiver, prefix):
346 354 files = self.files()
347 355 total = len(files)
348 356 relpath = subrelpath(self)
349 357 ui.progress(_('archiving (%s)') % relpath, 0,
350 358 unit=_('files'), total=total)
351 359 for i, name in enumerate(files):
352 360 flags = self.fileflags(name)
353 361 mode = 'x' in flags and 0755 or 0644
354 362 symlink = 'l' in flags
355 363 archiver.addfile(os.path.join(prefix, self._path, name),
356 364 mode, symlink, self.filedata(name))
357 365 ui.progress(_('archiving (%s)') % relpath, i + 1,
358 366 unit=_('files'), total=total)
359 367 ui.progress(_('archiving (%s)') % relpath, None)
360 368
361 369 def walk(self, match):
362 370 '''
363 371 walk recursively through the directory tree, finding all files
364 372 matched by the match function
365 373 '''
366 374 pass
367 375
368 376 def forget(self, ui, match, prefix):
369 377 return ([], [])
370 378
371 379 def revert(self, ui, substate, *pats, **opts):
372 380 ui.warn('%s: reverting %s subrepos is unsupported\n' \
373 381 % (substate[0], substate[2]))
374 382 return []
375 383
376 384 class hgsubrepo(abstractsubrepo):
377 385 def __init__(self, ctx, path, state):
378 386 self._path = path
379 387 self._state = state
380 388 r = ctx._repo
381 389 root = r.wjoin(path)
382 390 create = False
383 391 if not os.path.exists(os.path.join(root, '.hg')):
384 392 create = True
385 393 util.makedirs(root)
386 394 self._repo = hg.repository(r.ui, root, create=create)
387 395 self._initrepo(r, state[0], create)
388 396
389 397 def _initrepo(self, parentrepo, source, create):
390 398 self._repo._subparent = parentrepo
391 399 self._repo._subsource = source
392 400
393 401 if create:
394 402 fp = self._repo.opener("hgrc", "w", text=True)
395 403 fp.write('[paths]\n')
396 404
397 405 def addpathconfig(key, value):
398 406 if value:
399 407 fp.write('%s = %s\n' % (key, value))
400 408 self._repo.ui.setconfig('paths', key, value)
401 409
402 410 defpath = _abssource(self._repo, abort=False)
403 411 defpushpath = _abssource(self._repo, True, abort=False)
404 412 addpathconfig('default', defpath)
405 413 if defpath != defpushpath:
406 414 addpathconfig('default-push', defpushpath)
407 415 fp.close()
408 416
409 417 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
410 418 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
411 419 os.path.join(prefix, self._path), explicitonly)
412 420
413 421 def status(self, rev2, **opts):
414 422 try:
415 423 rev1 = self._state[1]
416 424 ctx1 = self._repo[rev1]
417 425 ctx2 = self._repo[rev2]
418 426 return self._repo.status(ctx1, ctx2, **opts)
419 427 except error.RepoLookupError, inst:
420 428 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
421 429 % (inst, subrelpath(self)))
422 430 return [], [], [], [], [], [], []
423 431
424 432 def diff(self, diffopts, node2, match, prefix, **opts):
425 433 try:
426 434 node1 = node.bin(self._state[1])
427 435 # We currently expect node2 to come from substate and be
428 436 # in hex format
429 437 if node2 is not None:
430 438 node2 = node.bin(node2)
431 439 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
432 440 node1, node2, match,
433 441 prefix=os.path.join(prefix, self._path),
434 442 listsubrepos=True, **opts)
435 443 except error.RepoLookupError, inst:
436 444 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
437 445 % (inst, subrelpath(self)))
438 446
439 447 def archive(self, ui, archiver, prefix):
440 448 self._get(self._state + ('hg',))
441 449 abstractsubrepo.archive(self, ui, archiver, prefix)
442 450
443 451 rev = self._state[1]
444 452 ctx = self._repo[rev]
445 453 for subpath in ctx.substate:
446 454 s = subrepo(ctx, subpath)
447 455 s.archive(ui, archiver, os.path.join(prefix, self._path))
448 456
449 457 def dirty(self, ignoreupdate=False):
450 458 r = self._state[1]
451 459 if r == '' and not ignoreupdate: # no state recorded
452 460 return True
453 461 w = self._repo[None]
454 462 if r != w.p1().hex() and not ignoreupdate:
455 463 # different version checked out
456 464 return True
457 465 return w.dirty() # working directory changed
458 466
459 467 def basestate(self):
460 468 return self._repo['.'].hex()
461 469
462 470 def checknested(self, path):
463 471 return self._repo._checknested(self._repo.wjoin(path))
464 472
465 473 def commit(self, text, user, date):
466 474 # don't bother committing in the subrepo if it's only been
467 475 # updated
468 476 if not self.dirty(True):
469 477 return self._repo['.'].hex()
470 478 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
471 479 n = self._repo.commit(text, user, date)
472 480 if not n:
473 481 return self._repo['.'].hex() # different version checked out
474 482 return node.hex(n)
475 483
476 484 def remove(self):
477 485 # we can't fully delete the repository as it may contain
478 486 # local-only history
479 487 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
480 488 hg.clean(self._repo, node.nullid, False)
481 489
482 490 def _get(self, state):
483 491 source, revision, kind = state
484 492 if revision not in self._repo:
485 493 self._repo._subsource = source
486 494 srcurl = _abssource(self._repo)
487 495 other = hg.peer(self._repo.ui, {}, srcurl)
488 496 if len(self._repo) == 0:
489 497 self._repo.ui.status(_('cloning subrepo %s from %s\n')
490 498 % (subrelpath(self), srcurl))
491 499 parentrepo = self._repo._subparent
492 500 shutil.rmtree(self._repo.path)
493 501 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
494 502 self._repo.root, update=False)
495 503 self._initrepo(parentrepo, source, create=True)
496 504 else:
497 505 self._repo.ui.status(_('pulling subrepo %s from %s\n')
498 506 % (subrelpath(self), srcurl))
499 507 self._repo.pull(other)
500 508 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
501 509 srcurl)
502 510
503 511 def get(self, state, overwrite=False):
504 512 self._get(state)
505 513 source, revision, kind = state
506 514 self._repo.ui.debug("getting subrepo %s\n" % self._path)
507 515 hg.clean(self._repo, revision, False)
508 516
509 517 def merge(self, state):
510 518 self._get(state)
511 519 cur = self._repo['.']
512 520 dst = self._repo[state[1]]
513 521 anc = dst.ancestor(cur)
514 522
515 523 def mergefunc():
516 524 if anc == cur and dst.branch() == cur.branch():
517 525 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
518 526 hg.update(self._repo, state[1])
519 527 elif anc == dst:
520 528 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
521 529 else:
522 530 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
523 531 hg.merge(self._repo, state[1], remind=False)
524 532
525 533 wctx = self._repo[None]
526 534 if self.dirty():
527 535 if anc != dst:
528 536 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
529 537 mergefunc()
530 538 else:
531 539 mergefunc()
532 540 else:
533 541 mergefunc()
534 542
535 543 def push(self, opts):
536 544 force = opts.get('force')
537 545 newbranch = opts.get('new_branch')
538 546 ssh = opts.get('ssh')
539 547
540 548 # push subrepos depth-first for coherent ordering
541 549 c = self._repo['']
542 550 subs = c.substate # only repos that are committed
543 551 for s in sorted(subs):
544 552 if c.sub(s).push(opts) == 0:
545 553 return False
546 554
547 555 dsturl = _abssource(self._repo, True)
548 556 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
549 557 (subrelpath(self), dsturl))
550 558 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
551 559 return self._repo.push(other, force, newbranch=newbranch)
552 560
553 561 def outgoing(self, ui, dest, opts):
554 562 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
555 563
556 564 def incoming(self, ui, source, opts):
557 565 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
558 566
559 567 def files(self):
560 568 rev = self._state[1]
561 569 ctx = self._repo[rev]
562 570 return ctx.manifest()
563 571
564 572 def filedata(self, name):
565 573 rev = self._state[1]
566 574 return self._repo[rev][name].data()
567 575
568 576 def fileflags(self, name):
569 577 rev = self._state[1]
570 578 ctx = self._repo[rev]
571 579 return ctx.flags(name)
572 580
573 581 def walk(self, match):
574 582 ctx = self._repo[None]
575 583 return ctx.walk(match)
576 584
577 585 def forget(self, ui, match, prefix):
578 586 return cmdutil.forget(ui, self._repo, match,
579 587 os.path.join(prefix, self._path), True)
580 588
581 589 def revert(self, ui, substate, *pats, **opts):
582 590 # reverting a subrepo is a 2 step process:
583 591 # 1. if the no_backup is not set, revert all modified
584 592 # files inside the subrepo
585 593 # 2. update the subrepo to the revision specified in
586 594 # the corresponding substate dictionary
587 595 ui.status(_('reverting subrepo %s\n') % substate[0])
588 596 if not opts.get('no_backup'):
589 597 # Revert all files on the subrepo, creating backups
590 598 # Note that this will not recursively revert subrepos
591 599 # We could do it if there was a set:subrepos() predicate
592 600 opts = opts.copy()
593 601 opts['date'] = None
594 602 opts['rev'] = substate[1]
595 603
596 604 pats = []
597 605 if not opts['all']:
598 606 pats = ['set:modified()']
599 607 self.filerevert(ui, *pats, **opts)
600 608
601 609 # Update the repo to the revision specified in the given substate
602 610 self.get(substate, overwrite=True)
603 611
604 612 def filerevert(self, ui, *pats, **opts):
605 613 ctx = self._repo[opts['rev']]
606 614 parents = self._repo.dirstate.parents()
607 615 if opts['all']:
608 616 pats = ['set:modified()']
609 617 else:
610 618 pats = []
611 619 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
612 620
613 621 class svnsubrepo(abstractsubrepo):
614 622 def __init__(self, ctx, path, state):
615 623 self._path = path
616 624 self._state = state
617 625 self._ctx = ctx
618 626 self._ui = ctx._repo.ui
619 627 self._exe = util.findexe('svn')
620 628 if not self._exe:
621 629 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
622 630 % self._path)
623 631
624 632 def _svncommand(self, commands, filename='', failok=False):
625 633 cmd = [self._exe]
626 634 extrakw = {}
627 635 if not self._ui.interactive():
628 636 # Making stdin be a pipe should prevent svn from behaving
629 637 # interactively even if we can't pass --non-interactive.
630 638 extrakw['stdin'] = subprocess.PIPE
631 639 # Starting in svn 1.5 --non-interactive is a global flag
632 640 # instead of being per-command, but we need to support 1.4 so
633 641 # we have to be intelligent about what commands take
634 642 # --non-interactive.
635 643 if commands[0] in ('update', 'checkout', 'commit'):
636 644 cmd.append('--non-interactive')
637 645 cmd.extend(commands)
638 646 if filename is not None:
639 647 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
640 648 cmd.append(path)
641 649 env = dict(os.environ)
642 650 # Avoid localized output, preserve current locale for everything else.
643 651 env['LC_MESSAGES'] = 'C'
644 652 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
645 653 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
646 654 universal_newlines=True, env=env, **extrakw)
647 655 stdout, stderr = p.communicate()
648 656 stderr = stderr.strip()
649 657 if not failok:
650 658 if p.returncode:
651 659 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
652 660 if stderr:
653 661 self._ui.warn(stderr + '\n')
654 662 return stdout, stderr
655 663
656 664 @propertycache
657 665 def _svnversion(self):
658 666 output, err = self._svncommand(['--version'], filename=None)
659 667 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
660 668 if not m:
661 669 raise util.Abort(_('cannot retrieve svn tool version'))
662 670 return (int(m.group(1)), int(m.group(2)))
663 671
664 672 def _wcrevs(self):
665 673 # Get the working directory revision as well as the last
666 674 # commit revision so we can compare the subrepo state with
667 675 # both. We used to store the working directory one.
668 676 output, err = self._svncommand(['info', '--xml'])
669 677 doc = xml.dom.minidom.parseString(output)
670 678 entries = doc.getElementsByTagName('entry')
671 679 lastrev, rev = '0', '0'
672 680 if entries:
673 681 rev = str(entries[0].getAttribute('revision')) or '0'
674 682 commits = entries[0].getElementsByTagName('commit')
675 683 if commits:
676 684 lastrev = str(commits[0].getAttribute('revision')) or '0'
677 685 return (lastrev, rev)
678 686
679 687 def _wcrev(self):
680 688 return self._wcrevs()[0]
681 689
682 690 def _wcchanged(self):
683 691 """Return (changes, extchanges, missing) where changes is True
684 692 if the working directory was changed, extchanges is
685 693 True if any of these changes concern an external entry and missing
686 694 is True if any change is a missing entry.
687 695 """
688 696 output, err = self._svncommand(['status', '--xml'])
689 697 externals, changes, missing = [], [], []
690 698 doc = xml.dom.minidom.parseString(output)
691 699 for e in doc.getElementsByTagName('entry'):
692 700 s = e.getElementsByTagName('wc-status')
693 701 if not s:
694 702 continue
695 703 item = s[0].getAttribute('item')
696 704 props = s[0].getAttribute('props')
697 705 path = e.getAttribute('path')
698 706 if item == 'external':
699 707 externals.append(path)
700 708 elif item == 'missing':
701 709 missing.append(path)
702 710 if (item not in ('', 'normal', 'unversioned', 'external')
703 711 or props not in ('', 'none', 'normal')):
704 712 changes.append(path)
705 713 for path in changes:
706 714 for ext in externals:
707 715 if path == ext or path.startswith(ext + os.sep):
708 716 return True, True, bool(missing)
709 717 return bool(changes), False, bool(missing)
710 718
711 719 def dirty(self, ignoreupdate=False):
712 720 if not self._wcchanged()[0]:
713 721 if self._state[1] in self._wcrevs() or ignoreupdate:
714 722 return False
715 723 return True
716 724
717 725 def basestate(self):
718 726 lastrev, rev = self._wcrevs()
719 727 if lastrev != rev:
720 728 # Last committed rev is not the same than rev. We would
721 729 # like to take lastrev but we do not know if the subrepo
722 730 # URL exists at lastrev. Test it and fallback to rev it
723 731 # is not there.
724 732 try:
725 733 self._svncommand(['info', '%s@%s' % (self._state[0], lastrev)])
726 734 return lastrev
727 735 except error.Abort:
728 736 pass
729 737 return rev
730 738
731 739 def commit(self, text, user, date):
732 740 # user and date are out of our hands since svn is centralized
733 741 changed, extchanged, missing = self._wcchanged()
734 742 if not changed:
735 743 return self.basestate()
736 744 if extchanged:
737 745 # Do not try to commit externals
738 746 raise util.Abort(_('cannot commit svn externals'))
739 747 if missing:
740 748 # svn can commit with missing entries but aborting like hg
741 749 # seems a better approach.
742 750 raise util.Abort(_('cannot commit missing svn entries'))
743 751 commitinfo, err = self._svncommand(['commit', '-m', text])
744 752 self._ui.status(commitinfo)
745 753 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
746 754 if not newrev:
747 755 if not commitinfo.strip():
748 756 # Sometimes, our definition of "changed" differs from
749 757 # svn one. For instance, svn ignores missing files
750 758 # when committing. If there are only missing files, no
751 759 # commit is made, no output and no error code.
752 760 raise util.Abort(_('failed to commit svn changes'))
753 761 raise util.Abort(commitinfo.splitlines()[-1])
754 762 newrev = newrev.groups()[0]
755 763 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
756 764 return newrev
757 765
758 766 def remove(self):
759 767 if self.dirty():
760 768 self._ui.warn(_('not removing repo %s because '
761 769 'it has changes.\n' % self._path))
762 770 return
763 771 self._ui.note(_('removing subrepo %s\n') % self._path)
764 772
765 773 def onerror(function, path, excinfo):
766 774 if function is not os.remove:
767 775 raise
768 776 # read-only files cannot be unlinked under Windows
769 777 s = os.stat(path)
770 778 if (s.st_mode & stat.S_IWRITE) != 0:
771 779 raise
772 780 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
773 781 os.remove(path)
774 782
775 783 path = self._ctx._repo.wjoin(self._path)
776 784 shutil.rmtree(path, onerror=onerror)
777 785 try:
778 786 os.removedirs(os.path.dirname(path))
779 787 except OSError:
780 788 pass
781 789
782 790 def get(self, state, overwrite=False):
783 791 if overwrite:
784 792 self._svncommand(['revert', '--recursive'])
785 793 args = ['checkout']
786 794 if self._svnversion >= (1, 5):
787 795 args.append('--force')
788 796 # The revision must be specified at the end of the URL to properly
789 797 # update to a directory which has since been deleted and recreated.
790 798 args.append('%s@%s' % (state[0], state[1]))
791 799 status, err = self._svncommand(args, failok=True)
792 800 if not re.search('Checked out revision [0-9]+.', status):
793 801 if ('is already a working copy for a different URL' in err
794 802 and (self._wcchanged()[:2] == (False, False))):
795 803 # obstructed but clean working copy, so just blow it away.
796 804 self.remove()
797 805 self.get(state, overwrite=False)
798 806 return
799 807 raise util.Abort((status or err).splitlines()[-1])
800 808 self._ui.status(status)
801 809
802 810 def merge(self, state):
803 811 old = self._state[1]
804 812 new = state[1]
805 813 wcrev = self._wcrev()
806 814 if new != wcrev:
807 815 dirty = old == wcrev or self._wcchanged()[0]
808 816 if _updateprompt(self._ui, self, dirty, wcrev, new):
809 817 self.get(state, False)
810 818
811 819 def push(self, opts):
812 820 # push is a no-op for SVN
813 821 return True
814 822
815 823 def files(self):
816 824 output = self._svncommand(['list', '--recursive', '--xml'])[0]
817 825 doc = xml.dom.minidom.parseString(output)
818 826 paths = []
819 827 for e in doc.getElementsByTagName('entry'):
820 828 kind = str(e.getAttribute('kind'))
821 829 if kind != 'file':
822 830 continue
823 831 name = ''.join(c.data for c
824 832 in e.getElementsByTagName('name')[0].childNodes
825 833 if c.nodeType == c.TEXT_NODE)
826 834 paths.append(name)
827 835 return paths
828 836
829 837 def filedata(self, name):
830 838 return self._svncommand(['cat'], name)[0]
831 839
832 840
833 841 class gitsubrepo(abstractsubrepo):
834 842 def __init__(self, ctx, path, state):
835 843 # TODO add git version check.
836 844 self._state = state
837 845 self._ctx = ctx
838 846 self._path = path
839 847 self._relpath = os.path.join(reporelpath(ctx._repo), path)
840 848 self._abspath = ctx._repo.wjoin(path)
841 849 self._subparent = ctx._repo
842 850 self._ui = ctx._repo.ui
843 851
844 852 def _gitcommand(self, commands, env=None, stream=False):
845 853 return self._gitdir(commands, env=env, stream=stream)[0]
846 854
847 855 def _gitdir(self, commands, env=None, stream=False):
848 856 return self._gitnodir(commands, env=env, stream=stream,
849 857 cwd=self._abspath)
850 858
851 859 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
852 860 """Calls the git command
853 861
854 862 The methods tries to call the git command. versions previor to 1.6.0
855 863 are not supported and very probably fail.
856 864 """
857 865 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
858 866 # unless ui.quiet is set, print git's stderr,
859 867 # which is mostly progress and useful info
860 868 errpipe = None
861 869 if self._ui.quiet:
862 870 errpipe = open(os.devnull, 'w')
863 871 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
864 872 close_fds=util.closefds,
865 873 stdout=subprocess.PIPE, stderr=errpipe)
866 874 if stream:
867 875 return p.stdout, None
868 876
869 877 retdata = p.stdout.read().strip()
870 878 # wait for the child to exit to avoid race condition.
871 879 p.wait()
872 880
873 881 if p.returncode != 0 and p.returncode != 1:
874 882 # there are certain error codes that are ok
875 883 command = commands[0]
876 884 if command in ('cat-file', 'symbolic-ref'):
877 885 return retdata, p.returncode
878 886 # for all others, abort
879 887 raise util.Abort('git %s error %d in %s' %
880 888 (command, p.returncode, self._relpath))
881 889
882 890 return retdata, p.returncode
883 891
884 892 def _gitmissing(self):
885 893 return not os.path.exists(os.path.join(self._abspath, '.git'))
886 894
887 895 def _gitstate(self):
888 896 return self._gitcommand(['rev-parse', 'HEAD'])
889 897
890 898 def _gitcurrentbranch(self):
891 899 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
892 900 if err:
893 901 current = None
894 902 return current
895 903
896 904 def _gitremote(self, remote):
897 905 out = self._gitcommand(['remote', 'show', '-n', remote])
898 906 line = out.split('\n')[1]
899 907 i = line.index('URL: ') + len('URL: ')
900 908 return line[i:]
901 909
902 910 def _githavelocally(self, revision):
903 911 out, code = self._gitdir(['cat-file', '-e', revision])
904 912 return code == 0
905 913
906 914 def _gitisancestor(self, r1, r2):
907 915 base = self._gitcommand(['merge-base', r1, r2])
908 916 return base == r1
909 917
910 918 def _gitisbare(self):
911 919 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
912 920
913 921 def _gitupdatestat(self):
914 922 """This must be run before git diff-index.
915 923 diff-index only looks at changes to file stat;
916 924 this command looks at file contents and updates the stat."""
917 925 self._gitcommand(['update-index', '-q', '--refresh'])
918 926
919 927 def _gitbranchmap(self):
920 928 '''returns 2 things:
921 929 a map from git branch to revision
922 930 a map from revision to branches'''
923 931 branch2rev = {}
924 932 rev2branch = {}
925 933
926 934 out = self._gitcommand(['for-each-ref', '--format',
927 935 '%(objectname) %(refname)'])
928 936 for line in out.split('\n'):
929 937 revision, ref = line.split(' ')
930 938 if (not ref.startswith('refs/heads/') and
931 939 not ref.startswith('refs/remotes/')):
932 940 continue
933 941 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
934 942 continue # ignore remote/HEAD redirects
935 943 branch2rev[ref] = revision
936 944 rev2branch.setdefault(revision, []).append(ref)
937 945 return branch2rev, rev2branch
938 946
939 947 def _gittracking(self, branches):
940 948 'return map of remote branch to local tracking branch'
941 949 # assumes no more than one local tracking branch for each remote
942 950 tracking = {}
943 951 for b in branches:
944 952 if b.startswith('refs/remotes/'):
945 953 continue
946 954 bname = b.split('/', 2)[2]
947 955 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
948 956 if remote:
949 957 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
950 958 tracking['refs/remotes/%s/%s' %
951 959 (remote, ref.split('/', 2)[2])] = b
952 960 return tracking
953 961
954 962 def _abssource(self, source):
955 963 if '://' not in source:
956 964 # recognize the scp syntax as an absolute source
957 965 colon = source.find(':')
958 966 if colon != -1 and '/' not in source[:colon]:
959 967 return source
960 968 self._subsource = source
961 969 return _abssource(self)
962 970
963 971 def _fetch(self, source, revision):
964 972 if self._gitmissing():
965 973 source = self._abssource(source)
966 974 self._ui.status(_('cloning subrepo %s from %s\n') %
967 975 (self._relpath, source))
968 976 self._gitnodir(['clone', source, self._abspath])
969 977 if self._githavelocally(revision):
970 978 return
971 979 self._ui.status(_('pulling subrepo %s from %s\n') %
972 980 (self._relpath, self._gitremote('origin')))
973 981 # try only origin: the originally cloned repo
974 982 self._gitcommand(['fetch'])
975 983 if not self._githavelocally(revision):
976 984 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
977 985 (revision, self._relpath))
978 986
979 987 def dirty(self, ignoreupdate=False):
980 988 if self._gitmissing():
981 989 return self._state[1] != ''
982 990 if self._gitisbare():
983 991 return True
984 992 if not ignoreupdate and self._state[1] != self._gitstate():
985 993 # different version checked out
986 994 return True
987 995 # check for staged changes or modified files; ignore untracked files
988 996 self._gitupdatestat()
989 997 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
990 998 return code == 1
991 999
992 1000 def basestate(self):
993 1001 return self._gitstate()
994 1002
995 1003 def get(self, state, overwrite=False):
996 1004 source, revision, kind = state
997 1005 if not revision:
998 1006 self.remove()
999 1007 return
1000 1008 self._fetch(source, revision)
1001 1009 # if the repo was set to be bare, unbare it
1002 1010 if self._gitisbare():
1003 1011 self._gitcommand(['config', 'core.bare', 'false'])
1004 1012 if self._gitstate() == revision:
1005 1013 self._gitcommand(['reset', '--hard', 'HEAD'])
1006 1014 return
1007 1015 elif self._gitstate() == revision:
1008 1016 if overwrite:
1009 1017 # first reset the index to unmark new files for commit, because
1010 1018 # reset --hard will otherwise throw away files added for commit,
1011 1019 # not just unmark them.
1012 1020 self._gitcommand(['reset', 'HEAD'])
1013 1021 self._gitcommand(['reset', '--hard', 'HEAD'])
1014 1022 return
1015 1023 branch2rev, rev2branch = self._gitbranchmap()
1016 1024
1017 1025 def checkout(args):
1018 1026 cmd = ['checkout']
1019 1027 if overwrite:
1020 1028 # first reset the index to unmark new files for commit, because
1021 1029 # the -f option will otherwise throw away files added for
1022 1030 # commit, not just unmark them.
1023 1031 self._gitcommand(['reset', 'HEAD'])
1024 1032 cmd.append('-f')
1025 1033 self._gitcommand(cmd + args)
1026 1034
1027 1035 def rawcheckout():
1028 1036 # no branch to checkout, check it out with no branch
1029 1037 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1030 1038 self._relpath)
1031 1039 self._ui.warn(_('check out a git branch if you intend '
1032 1040 'to make changes\n'))
1033 1041 checkout(['-q', revision])
1034 1042
1035 1043 if revision not in rev2branch:
1036 1044 rawcheckout()
1037 1045 return
1038 1046 branches = rev2branch[revision]
1039 1047 firstlocalbranch = None
1040 1048 for b in branches:
1041 1049 if b == 'refs/heads/master':
1042 1050 # master trumps all other branches
1043 1051 checkout(['refs/heads/master'])
1044 1052 return
1045 1053 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1046 1054 firstlocalbranch = b
1047 1055 if firstlocalbranch:
1048 1056 checkout([firstlocalbranch])
1049 1057 return
1050 1058
1051 1059 tracking = self._gittracking(branch2rev.keys())
1052 1060 # choose a remote branch already tracked if possible
1053 1061 remote = branches[0]
1054 1062 if remote not in tracking:
1055 1063 for b in branches:
1056 1064 if b in tracking:
1057 1065 remote = b
1058 1066 break
1059 1067
1060 1068 if remote not in tracking:
1061 1069 # create a new local tracking branch
1062 1070 local = remote.split('/', 2)[2]
1063 1071 checkout(['-b', local, remote])
1064 1072 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1065 1073 # When updating to a tracked remote branch,
1066 1074 # if the local tracking branch is downstream of it,
1067 1075 # a normal `git pull` would have performed a "fast-forward merge"
1068 1076 # which is equivalent to updating the local branch to the remote.
1069 1077 # Since we are only looking at branching at update, we need to
1070 1078 # detect this situation and perform this action lazily.
1071 1079 if tracking[remote] != self._gitcurrentbranch():
1072 1080 checkout([tracking[remote]])
1073 1081 self._gitcommand(['merge', '--ff', remote])
1074 1082 else:
1075 1083 # a real merge would be required, just checkout the revision
1076 1084 rawcheckout()
1077 1085
1078 1086 def commit(self, text, user, date):
1079 1087 if self._gitmissing():
1080 1088 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1081 1089 cmd = ['commit', '-a', '-m', text]
1082 1090 env = os.environ.copy()
1083 1091 if user:
1084 1092 cmd += ['--author', user]
1085 1093 if date:
1086 1094 # git's date parser silently ignores when seconds < 1e9
1087 1095 # convert to ISO8601
1088 1096 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1089 1097 '%Y-%m-%dT%H:%M:%S %1%2')
1090 1098 self._gitcommand(cmd, env=env)
1091 1099 # make sure commit works otherwise HEAD might not exist under certain
1092 1100 # circumstances
1093 1101 return self._gitstate()
1094 1102
1095 1103 def merge(self, state):
1096 1104 source, revision, kind = state
1097 1105 self._fetch(source, revision)
1098 1106 base = self._gitcommand(['merge-base', revision, self._state[1]])
1099 1107 self._gitupdatestat()
1100 1108 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1101 1109
1102 1110 def mergefunc():
1103 1111 if base == revision:
1104 1112 self.get(state) # fast forward merge
1105 1113 elif base != self._state[1]:
1106 1114 self._gitcommand(['merge', '--no-commit', revision])
1107 1115
1108 1116 if self.dirty():
1109 1117 if self._gitstate() != revision:
1110 1118 dirty = self._gitstate() == self._state[1] or code != 0
1111 1119 if _updateprompt(self._ui, self, dirty,
1112 1120 self._state[1][:7], revision[:7]):
1113 1121 mergefunc()
1114 1122 else:
1115 1123 mergefunc()
1116 1124
1117 1125 def push(self, opts):
1118 1126 force = opts.get('force')
1119 1127
1120 1128 if not self._state[1]:
1121 1129 return True
1122 1130 if self._gitmissing():
1123 1131 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1124 1132 # if a branch in origin contains the revision, nothing to do
1125 1133 branch2rev, rev2branch = self._gitbranchmap()
1126 1134 if self._state[1] in rev2branch:
1127 1135 for b in rev2branch[self._state[1]]:
1128 1136 if b.startswith('refs/remotes/origin/'):
1129 1137 return True
1130 1138 for b, revision in branch2rev.iteritems():
1131 1139 if b.startswith('refs/remotes/origin/'):
1132 1140 if self._gitisancestor(self._state[1], revision):
1133 1141 return True
1134 1142 # otherwise, try to push the currently checked out branch
1135 1143 cmd = ['push']
1136 1144 if force:
1137 1145 cmd.append('--force')
1138 1146
1139 1147 current = self._gitcurrentbranch()
1140 1148 if current:
1141 1149 # determine if the current branch is even useful
1142 1150 if not self._gitisancestor(self._state[1], current):
1143 1151 self._ui.warn(_('unrelated git branch checked out '
1144 1152 'in subrepo %s\n') % self._relpath)
1145 1153 return False
1146 1154 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1147 1155 (current.split('/', 2)[2], self._relpath))
1148 1156 self._gitcommand(cmd + ['origin', current])
1149 1157 return True
1150 1158 else:
1151 1159 self._ui.warn(_('no branch checked out in subrepo %s\n'
1152 1160 'cannot push revision %s') %
1153 1161 (self._relpath, self._state[1]))
1154 1162 return False
1155 1163
1156 1164 def remove(self):
1157 1165 if self._gitmissing():
1158 1166 return
1159 1167 if self.dirty():
1160 1168 self._ui.warn(_('not removing repo %s because '
1161 1169 'it has changes.\n') % self._relpath)
1162 1170 return
1163 1171 # we can't fully delete the repository as it may contain
1164 1172 # local-only history
1165 1173 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1166 1174 self._gitcommand(['config', 'core.bare', 'true'])
1167 1175 for f in os.listdir(self._abspath):
1168 1176 if f == '.git':
1169 1177 continue
1170 1178 path = os.path.join(self._abspath, f)
1171 1179 if os.path.isdir(path) and not os.path.islink(path):
1172 1180 shutil.rmtree(path)
1173 1181 else:
1174 1182 os.remove(path)
1175 1183
1176 1184 def archive(self, ui, archiver, prefix):
1177 1185 source, revision = self._state
1178 1186 if not revision:
1179 1187 return
1180 1188 self._fetch(source, revision)
1181 1189
1182 1190 # Parse git's native archive command.
1183 1191 # This should be much faster than manually traversing the trees
1184 1192 # and objects with many subprocess calls.
1185 1193 tarstream = self._gitcommand(['archive', revision], stream=True)
1186 1194 tar = tarfile.open(fileobj=tarstream, mode='r|')
1187 1195 relpath = subrelpath(self)
1188 1196 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1189 1197 for i, info in enumerate(tar):
1190 1198 if info.isdir():
1191 1199 continue
1192 1200 if info.issym():
1193 1201 data = info.linkname
1194 1202 else:
1195 1203 data = tar.extractfile(info).read()
1196 1204 archiver.addfile(os.path.join(prefix, self._path, info.name),
1197 1205 info.mode, info.issym(), data)
1198 1206 ui.progress(_('archiving (%s)') % relpath, i + 1,
1199 1207 unit=_('files'))
1200 1208 ui.progress(_('archiving (%s)') % relpath, None)
1201 1209
1202 1210
1203 1211 def status(self, rev2, **opts):
1204 1212 rev1 = self._state[1]
1205 1213 if self._gitmissing() or not rev1:
1206 1214 # if the repo is missing, return no results
1207 1215 return [], [], [], [], [], [], []
1208 1216 modified, added, removed = [], [], []
1209 1217 self._gitupdatestat()
1210 1218 if rev2:
1211 1219 command = ['diff-tree', rev1, rev2]
1212 1220 else:
1213 1221 command = ['diff-index', rev1]
1214 1222 out = self._gitcommand(command)
1215 1223 for line in out.split('\n'):
1216 1224 tab = line.find('\t')
1217 1225 if tab == -1:
1218 1226 continue
1219 1227 status, f = line[tab - 1], line[tab + 1:]
1220 1228 if status == 'M':
1221 1229 modified.append(f)
1222 1230 elif status == 'A':
1223 1231 added.append(f)
1224 1232 elif status == 'D':
1225 1233 removed.append(f)
1226 1234
1227 1235 deleted = unknown = ignored = clean = []
1228 1236 return modified, added, removed, deleted, unknown, ignored, clean
1229 1237
1230 1238 types = {
1231 1239 'hg': hgsubrepo,
1232 1240 'svn': svnsubrepo,
1233 1241 'git': gitsubrepo,
1234 1242 }
@@ -1,636 +1,633 b''
1 1 $ check_code="$TESTDIR"/../contrib/check-code.py
2 2 $ cd "$TESTDIR"/..
3 3 $ if hg identify -q > /dev/null; then :
4 4 > else
5 5 > echo "skipped: not a Mercurial working dir" >&2
6 6 > exit 80
7 7 > fi
8 8 $ hg manifest | xargs "$check_code" || echo 'FAILURE IS NOT AN OPTION!!!'
9 9
10 10 $ hg manifest | xargs "$check_code" --warnings --nolineno --per-file=0 || true
11 11 contrib/check-code.py:0:
12 12 > # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', "don't use underbars in identifiers"),
13 13 warning: line over 80 characters
14 14 contrib/perf.py:0:
15 15 > except:
16 16 warning: naked except clause
17 17 contrib/perf.py:0:
18 18 > #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, False))))
19 19 warning: line over 80 characters
20 20 contrib/perf.py:0:
21 21 > except:
22 22 warning: naked except clause
23 23 contrib/setup3k.py:0:
24 24 > except:
25 25 warning: naked except clause
26 26 contrib/setup3k.py:0:
27 27 > except:
28 28 warning: naked except clause
29 29 contrib/setup3k.py:0:
30 30 > except:
31 31 warning: naked except clause
32 32 warning: naked except clause
33 33 warning: naked except clause
34 34 contrib/shrink-revlog.py:0:
35 35 > except:
36 36 warning: naked except clause
37 37 doc/gendoc.py:0:
38 38 > "together with Mercurial. Help for other extensions is available "
39 39 warning: line over 80 characters
40 40 hgext/bugzilla.py:0:
41 41 > raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
42 42 warning: line over 80 characters
43 43 hgext/bugzilla.py:0:
44 44 > bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
45 45 warning: line over 80 characters
46 46 hgext/convert/__init__.py:0:
47 47 > ('', 'ancestors', '', _('show current changeset in ancestor branches')),
48 48 warning: line over 80 characters
49 49 hgext/convert/bzr.py:0:
50 50 > except:
51 51 warning: naked except clause
52 52 hgext/convert/common.py:0:
53 53 > except:
54 54 warning: naked except clause
55 55 hgext/convert/common.py:0:
56 56 > except:
57 57 warning: naked except clause
58 58 warning: naked except clause
59 59 hgext/convert/convcmd.py:0:
60 60 > except:
61 61 warning: naked except clause
62 62 hgext/convert/cvs.py:0:
63 63 > # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
64 64 warning: line over 80 characters
65 65 hgext/convert/cvsps.py:0:
66 66 > assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
67 67 warning: line over 80 characters
68 68 hgext/convert/cvsps.py:0:
69 69 > ui.write('Ancestors: %s\n' % (','.join(r)))
70 70 warning: unwrapped ui message
71 71 hgext/convert/cvsps.py:0:
72 72 > ui.write('Parent: %d\n' % cs.parents[0].id)
73 73 warning: unwrapped ui message
74 74 hgext/convert/cvsps.py:0:
75 75 > ui.write('Parents: %s\n' %
76 76 warning: unwrapped ui message
77 77 hgext/convert/cvsps.py:0:
78 78 > except:
79 79 warning: naked except clause
80 80 hgext/convert/cvsps.py:0:
81 81 > ui.write('Branchpoints: %s \n' % ', '.join(branchpoints))
82 82 warning: unwrapped ui message
83 83 hgext/convert/cvsps.py:0:
84 84 > ui.write('Author: %s\n' % cs.author)
85 85 warning: unwrapped ui message
86 86 hgext/convert/cvsps.py:0:
87 87 > ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
88 88 warning: unwrapped ui message
89 89 hgext/convert/cvsps.py:0:
90 90 > ui.write('Date: %s\n' % util.datestr(cs.date,
91 91 warning: unwrapped ui message
92 92 hgext/convert/cvsps.py:0:
93 93 > ui.write('Log:\n')
94 94 warning: unwrapped ui message
95 95 hgext/convert/cvsps.py:0:
96 96 > ui.write('Members: \n')
97 97 warning: unwrapped ui message
98 98 hgext/convert/cvsps.py:0:
99 99 > ui.write('PatchSet %d \n' % cs.id)
100 100 warning: unwrapped ui message
101 101 hgext/convert/cvsps.py:0:
102 102 > ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1],
103 103 warning: unwrapped ui message
104 104 hgext/convert/git.py:0:
105 105 > except:
106 106 warning: naked except clause
107 107 hgext/convert/git.py:0:
108 108 > fh = self.gitopen('git diff-tree --name-only --root -r %s "%s^%s" --'
109 109 warning: line over 80 characters
110 110 hgext/convert/hg.py:0:
111 111 > # detect missing revlogs and abort on errors or populate self.ignored
112 112 warning: line over 80 characters
113 113 hgext/convert/hg.py:0:
114 114 > except:
115 115 warning: naked except clause
116 116 warning: naked except clause
117 117 hgext/convert/hg.py:0:
118 118 > except:
119 119 warning: naked except clause
120 120 hgext/convert/monotone.py:0:
121 121 > except:
122 122 warning: naked except clause
123 123 hgext/convert/monotone.py:0:
124 124 > except:
125 125 warning: naked except clause
126 126 hgext/convert/subversion.py:0:
127 127 > raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
128 128 warning: line over 80 characters
129 129 hgext/convert/subversion.py:0:
130 130 > except:
131 131 warning: naked except clause
132 132 hgext/convert/subversion.py:0:
133 133 > args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
134 134 warning: line over 80 characters
135 135 hgext/convert/subversion.py:0:
136 136 > self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/')
137 137 warning: line over 80 characters
138 138 hgext/convert/subversion.py:0:
139 139 > except:
140 140 warning: naked except clause
141 141 hgext/convert/subversion.py:0:
142 142 > def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
143 143 warning: line over 80 characters
144 144 hgext/eol.py:0:
145 145 > if ui.configbool('eol', 'fix-trailing-newline', False) and s and s[-1] != '\n':
146 146 warning: line over 80 characters
147 147 warning: line over 80 characters
148 148 hgext/gpg.py:0:
149 149 > except:
150 150 warning: naked except clause
151 151 hgext/hgcia.py:0:
152 152 > except:
153 153 warning: naked except clause
154 154 hgext/hgk.py:0:
155 155 > ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
156 156 warning: line over 80 characters
157 157 hgext/hgk.py:0:
158 158 > ui.write("parent %s\n" % p)
159 159 warning: unwrapped ui message
160 160 hgext/hgk.py:0:
161 161 > ui.write('k=%s\nv=%s\n' % (name, value))
162 162 warning: unwrapped ui message
163 163 hgext/hgk.py:0:
164 164 > ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
165 165 warning: unwrapped ui message
166 166 hgext/hgk.py:0:
167 167 > ui.write("branch %s\n\n" % ctx.branch())
168 168 warning: unwrapped ui message
169 169 hgext/hgk.py:0:
170 170 > ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
171 171 warning: unwrapped ui message
172 172 hgext/hgk.py:0:
173 173 > ui.write("revision %d\n" % ctx.rev())
174 174 warning: unwrapped ui message
175 175 hgext/hgk.py:0:
176 176 > ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
177 177 warning: line over 80 characters
178 178 warning: unwrapped ui message
179 179 hgext/highlight/__init__.py:0:
180 180 > extensions.wrapfunction(webcommands, '_filerevision', filerevision_highlight)
181 181 warning: line over 80 characters
182 182 hgext/highlight/__init__.py:0:
183 183 > return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')]
184 184 warning: line over 80 characters
185 185 hgext/inotify/__init__.py:0:
186 186 > if self._inotifyon and not ignored and not subrepos and not self._dirty:
187 187 warning: line over 80 characters
188 188 hgext/inotify/server.py:0:
189 189 > except:
190 190 warning: naked except clause
191 191 hgext/inotify/server.py:0:
192 192 > except:
193 193 warning: naked except clause
194 194 hgext/keyword.py:0:
195 195 > ui.note("hg ci -m '%s'\n" % msg)
196 196 warning: unwrapped ui message
197 197 hgext/mq.py:0:
198 198 > raise util.Abort(_("cannot push --exact with applied patches"))
199 199 warning: line over 80 characters
200 200 hgext/mq.py:0:
201 201 > raise util.Abort(_("cannot use --exact and --move together"))
202 202 warning: line over 80 characters
203 203 hgext/mq.py:0:
204 204 > self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
205 205 warning: line over 80 characters
206 206 hgext/mq.py:0:
207 207 > except:
208 208 warning: naked except clause
209 209 warning: naked except clause
210 210 hgext/mq.py:0:
211 211 > except:
212 212 warning: naked except clause
213 213 warning: naked except clause
214 214 warning: naked except clause
215 215 warning: naked except clause
216 216 hgext/mq.py:0:
217 217 > raise util.Abort(_('cannot mix -l/--list with options or arguments'))
218 218 warning: line over 80 characters
219 219 hgext/mq.py:0:
220 220 > raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
221 221 warning: line over 80 characters
222 222 hgext/mq.py:0:
223 223 > ('', 'move', None, _('reorder patch series and apply only the patch'))],
224 224 warning: line over 80 characters
225 225 hgext/mq.py:0:
226 226 > ('U', 'noupdate', None, _('do not update the new working directories')),
227 227 warning: line over 80 characters
228 228 hgext/mq.py:0:
229 229 > ('e', 'exact', None, _('apply the target patch to its recorded parent')),
230 230 warning: line over 80 characters
231 231 hgext/mq.py:0:
232 232 > except:
233 233 warning: naked except clause
234 234 hgext/mq.py:0:
235 235 > ui.write("mq: %s\n" % ', '.join(m))
236 236 warning: unwrapped ui message
237 237 hgext/mq.py:0:
238 238 > repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
239 239 warning: line over 80 characters
240 240 hgext/notify.py:0:
241 241 > ui.note(_('notify: suppressing notification for merge %d:%s\n') %
242 242 warning: line over 80 characters
243 243 hgext/patchbomb.py:0:
244 244 > binnode, seqno=idx, total=total)
245 245 warning: line over 80 characters
246 246 hgext/patchbomb.py:0:
247 247 > except:
248 248 warning: naked except clause
249 249 hgext/patchbomb.py:0:
250 250 > ui.write('Subject: %s\n' % subj)
251 251 warning: unwrapped ui message
252 252 hgext/patchbomb.py:0:
253 253 > p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', opts.get('test'))
254 254 warning: line over 80 characters
255 255 hgext/patchbomb.py:0:
256 256 > ui.write('From: %s\n' % sender)
257 257 warning: unwrapped ui message
258 258 hgext/record.py:0:
259 259 > ignoreblanklines=opts.get('ignore_blank_lines'))
260 260 warning: line over 80 characters
261 261 hgext/record.py:0:
262 262 > ignorewsamount=opts.get('ignore_space_change'),
263 263 warning: line over 80 characters
264 264 hgext/zeroconf/__init__.py:0:
265 265 > publish(name, desc, path, util.getport(u.config("web", "port", 8000)))
266 266 warning: line over 80 characters
267 267 hgext/zeroconf/__init__.py:0:
268 268 > except:
269 269 warning: naked except clause
270 270 warning: naked except clause
271 271 mercurial/bundlerepo.py:0:
272 272 > is a bundlerepo for the obtained bundle when the original "other" is remote.
273 273 warning: line over 80 characters
274 274 mercurial/bundlerepo.py:0:
275 275 > "local" is a local repo from which to obtain the actual incoming changesets; it
276 276 warning: line over 80 characters
277 277 mercurial/bundlerepo.py:0:
278 278 > tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
279 279 warning: line over 80 characters
280 280 mercurial/commands.py:0:
281 281 > " size " + basehdr + " link p1 p2 nodeid\n")
282 282 warning: line over 80 characters
283 283 mercurial/commands.py:0:
284 284 > raise util.Abort('cannot use localheads with old style discovery')
285 285 warning: line over 80 characters
286 286 mercurial/commands.py:0:
287 287 > ui.note('branch %s\n' % data)
288 288 warning: unwrapped ui message
289 289 mercurial/commands.py:0:
290 290 > ui.note('node %s\n' % str(data))
291 291 warning: unwrapped ui message
292 292 mercurial/commands.py:0:
293 293 > ui.note('tag %s\n' % name)
294 294 warning: unwrapped ui message
295 295 mercurial/commands.py:0:
296 296 > ui.write("unpruned common: %s\n" % " ".join([short(n)
297 297 warning: unwrapped ui message
298 298 mercurial/commands.py:0:
299 299 > yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
300 300 warning: line over 80 characters
301 301 mercurial/commands.py:0:
302 302 > yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
303 303 warning: line over 80 characters
304 304 mercurial/commands.py:0:
305 305 > except:
306 306 warning: naked except clause
307 307 mercurial/commands.py:0:
308 308 > ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
309 309 warning: line over 80 characters
310 310 mercurial/commands.py:0:
311 311 > ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
312 312 warning: unwrapped ui message
313 313 mercurial/commands.py:0:
314 314 > ui.write("local is subset\n")
315 315 warning: unwrapped ui message
316 316 mercurial/commands.py:0:
317 317 > ui.write("remote is subset\n")
318 318 warning: unwrapped ui message
319 319 mercurial/commands.py:0:
320 320 > ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
321 321 warning: line over 80 characters
322 322 mercurial/commands.py:0:
323 323 > ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
324 324 warning: line over 80 characters
325 325 mercurial/commands.py:0:
326 326 > ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
327 327 warning: line over 80 characters
328 328 mercurial/commands.py:0:
329 329 > ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
330 330 warning: line over 80 characters
331 331 warning: unwrapped ui message
332 332 mercurial/commands.py:0:
333 333 > ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
334 334 warning: unwrapped ui message
335 335 mercurial/commands.py:0:
336 336 > ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
337 337 warning: unwrapped ui message
338 338 mercurial/commands.py:0:
339 339 > except:
340 340 warning: naked except clause
341 341 mercurial/commands.py:0:
342 342 > revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
343 343 warning: line over 80 characters
344 344 mercurial/commands.py:0:
345 345 > ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
346 346 warning: unwrapped ui message
347 347 mercurial/commands.py:0:
348 348 > ui.write("match: %s\n" % m(d[0]))
349 349 warning: unwrapped ui message
350 350 mercurial/commands.py:0:
351 351 > ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
352 352 warning: unwrapped ui message
353 353 mercurial/commands.py:0:
354 354 > ui.write('path %s\n' % k)
355 355 warning: unwrapped ui message
356 356 mercurial/commands.py:0:
357 357 > ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
358 358 warning: unwrapped ui message
359 359 mercurial/commands.py:0:
360 360 > Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
361 361 warning: line over 80 characters
362 362 mercurial/commands.py:0:
363 363 > remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
364 364 warning: line over 80 characters
365 365 mercurial/commands.py:0:
366 366 > ui.write("digraph G {\n")
367 367 warning: unwrapped ui message
368 368 mercurial/commands.py:0:
369 369 > ui.write("internal: %s %s\n" % d)
370 370 warning: unwrapped ui message
371 371 mercurial/commands.py:0:
372 372 > ui.write("standard: %s\n" % util.datestr(d))
373 373 warning: unwrapped ui message
374 374 mercurial/commands.py:0:
375 375 > ui.write('avg chain length : ' + fmt % avgchainlen)
376 376 warning: unwrapped ui message
377 377 mercurial/commands.py:0:
378 378 > ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
379 379 warning: unwrapped ui message
380 380 mercurial/commands.py:0:
381 381 > ui.write('compression ratio : ' + fmt % compratio)
382 382 warning: unwrapped ui message
383 383 mercurial/commands.py:0:
384 384 > ui.write('delta size (min/max/avg) : %d / %d / %d\n'
385 385 warning: unwrapped ui message
386 386 mercurial/commands.py:0:
387 387 > ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
388 388 warning: unwrapped ui message
389 389 mercurial/commands.py:0:
390 390 > ui.write('flags : %s\n' % ', '.join(flags))
391 391 warning: unwrapped ui message
392 392 mercurial/commands.py:0:
393 393 > ui.write('format : %d\n' % format)
394 394 warning: unwrapped ui message
395 395 mercurial/commands.py:0:
396 396 > ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
397 397 warning: unwrapped ui message
398 398 mercurial/commands.py:0:
399 399 > ui.write('revision size : ' + fmt2 % totalsize)
400 400 warning: unwrapped ui message
401 401 mercurial/commands.py:0:
402 402 > ui.write('revisions : ' + fmt2 % numrevs)
403 403 warning: unwrapped ui message
404 404 warning: unwrapped ui message
405 405 mercurial/commands.py:0:
406 406 > ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
407 407 warning: unwrapped ui message
408 408 mercurial/commandserver.py:0:
409 409 > # the ui here is really the repo ui so take its baseui so we don't end up
410 410 warning: line over 80 characters
411 411 mercurial/context.py:0:
412 412 > return self._manifestdelta[path], self._manifestdelta.flags(path)
413 413 warning: line over 80 characters
414 414 mercurial/dagparser.py:0:
415 415 > raise util.Abort(_("invalid character in dag description: %s...") % s)
416 416 warning: line over 80 characters
417 417 mercurial/dagparser.py:0:
418 418 > >>> dagtext([('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))])
419 419 warning: line over 80 characters
420 420 mercurial/dirstate.py:0:
421 421 > if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
422 422 warning: line over 80 characters
423 423 mercurial/discovery.py:0:
424 424 > If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive)
425 425 warning: line over 80 characters
426 426 mercurial/discovery.py:0:
427 427 > def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None):
428 428 warning: line over 80 characters
429 429 mercurial/dispatch.py:0:
430 430 > " (.hg not found)") % os.getcwd())
431 431 warning: line over 80 characters
432 432 mercurial/dispatch.py:0:
433 433 > except:
434 434 warning: naked except clause
435 435 mercurial/dispatch.py:0:
436 436 > return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
437 437 warning: line over 80 characters
438 438 mercurial/dispatch.py:0:
439 439 > def __init__(self, args, ui=None, repo=None, fin=None, fout=None, ferr=None):
440 440 warning: line over 80 characters
441 441 mercurial/dispatch.py:0:
442 442 > except:
443 443 warning: naked except clause
444 444 mercurial/hg.py:0:
445 445 > except:
446 446 warning: naked except clause
447 447 mercurial/hgweb/hgweb_mod.py:0:
448 448 > self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
449 449 warning: line over 80 characters
450 450 mercurial/keepalive.py:0:
451 451 > except:
452 452 warning: naked except clause
453 453 mercurial/keepalive.py:0:
454 454 > except:
455 455 warning: naked except clause
456 456 mercurial/localrepo.py:0:
457 457 > # we return an integer indicating remote head count change
458 458 warning: line over 80 characters
459 459 mercurial/localrepo.py:0:
460 460 > raise util.Abort(_("empty or missing revlog for %s") % fname)
461 461 warning: line over 80 characters
462 462 warning: line over 80 characters
463 463 mercurial/localrepo.py:0:
464 464 > if self._tagscache.tagtypes and name in self._tagscache.tagtypes:
465 465 warning: line over 80 characters
466 466 mercurial/localrepo.py:0:
467 467 > self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
468 468 warning: line over 80 characters
469 469 mercurial/localrepo.py:0:
470 470 > # new requirements = old non-format requirements + new format-related
471 471 warning: line over 80 characters
472 472 mercurial/localrepo.py:0:
473 473 > except:
474 474 warning: naked except clause
475 475 mercurial/localrepo.py:0:
476 476 > """return status of files between two nodes or node and working directory
477 477 warning: line over 80 characters
478 478 mercurial/localrepo.py:0:
479 479 > '''Returns a tagscache object that contains various tags related caches.'''
480 480 warning: line over 80 characters
481 481 mercurial/manifest.py:0:
482 482 > return "".join(struct.pack(">lll", start, end, len(content)) + content
483 483 warning: line over 80 characters
484 484 mercurial/merge.py:0:
485 485 > subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
486 486 warning: line over 80 characters
487 487 mercurial/patch.py:0:
488 488 > modified, added, removed, copy, getfilectx, opts, losedata, prefix)
489 489 warning: line over 80 characters
490 490 mercurial/patch.py:0:
491 491 > diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
492 492 warning: line over 80 characters
493 493 mercurial/patch.py:0:
494 494 > output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
495 495 warning: line over 80 characters
496 496 mercurial/patch.py:0:
497 497 > except:
498 498 warning: naked except clause
499 mercurial/pure/base85.py:0:
500 > raise OverflowError('Base85 overflow in hunk starting at byte %d' % i)
501 warning: line over 80 characters
502 499 mercurial/pure/mpatch.py:0:
503 500 > frags.extend(reversed(new)) # what was left at the end
504 501 warning: line over 80 characters
505 502 mercurial/repair.py:0:
506 503 > except:
507 504 warning: naked except clause
508 505 mercurial/repair.py:0:
509 506 > except:
510 507 warning: naked except clause
511 508 mercurial/revset.py:0:
512 509 > elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
513 510 warning: line over 80 characters
514 511 mercurial/revset.py:0:
515 512 > Changesets that are the Nth ancestor (first parents only) of a changeset in set.
516 513 warning: line over 80 characters
517 514 mercurial/scmutil.py:0:
518 515 > raise util.Abort(_("path '%s' is inside nested repo %r") %
519 516 warning: line over 80 characters
520 517 mercurial/scmutil.py:0:
521 518 > "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
522 519 warning: line over 80 characters
523 520 mercurial/scmutil.py:0:
524 521 > elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
525 522 warning: line over 80 characters
526 523 mercurial/setdiscovery.py:0:
527 524 > # treat remote heads (and maybe own heads) as a first implicit sample response
528 525 warning: line over 80 characters
529 526 mercurial/setdiscovery.py:0:
530 527 > undecided = dag.nodeset() # own nodes where I don't know if remote knows them
531 528 warning: line over 80 characters
532 529 mercurial/similar.py:0:
533 530 > repo.ui.progress(_('searching for similar files'), i, total=len(removed))
534 531 warning: line over 80 characters
535 532 mercurial/simplemerge.py:0:
536 533 > for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
537 534 warning: line over 80 characters
538 535 mercurial/sshrepo.py:0:
539 536 > self._abort(error.RepoError(_("no suitable response from remote hg")))
540 537 warning: line over 80 characters
541 538 mercurial/sshrepo.py:0:
542 539 > except:
543 540 warning: naked except clause
544 541 mercurial/subrepo.py:0:
545 542 > other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
546 543 warning: line over 80 characters
547 544 mercurial/subrepo.py:0:
548 545 > msg = (_(' subrepository sources for %s differ (in checked out version)\n'
549 546 warning: line over 80 characters
550 547 mercurial/transaction.py:0:
551 548 > except:
552 549 warning: naked except clause
553 550 mercurial/ui.py:0:
554 551 > traceback.print_exception(exc[0], exc[1], exc[2], file=self.ferr)
555 552 warning: line over 80 characters
556 553 mercurial/url.py:0:
557 554 > conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
558 555 warning: line over 80 characters
559 556 mercurial/util.py:0:
560 557 > except:
561 558 warning: naked except clause
562 559 mercurial/util.py:0:
563 560 > except:
564 561 warning: naked except clause
565 562 mercurial/verify.py:0:
566 563 > except:
567 564 warning: naked except clause
568 565 mercurial/verify.py:0:
569 566 > except:
570 567 warning: naked except clause
571 568 mercurial/wireproto.py:0:
572 569 > # Assuming the future to be filled with the result from the batched request
573 570 warning: line over 80 characters
574 571 mercurial/wireproto.py:0:
575 572 > '''remote must support _submitbatch(encbatch) and _submitone(op, encargs)'''
576 573 warning: line over 80 characters
577 574 mercurial/wireproto.py:0:
578 575 > All methods invoked on instances of this class are simply queued and return a
579 576 warning: line over 80 characters
580 577 mercurial/wireproto.py:0:
581 578 > The decorator returns a function which wraps this coroutine as a plain method,
582 579 warning: line over 80 characters
583 580 setup.py:0:
584 581 > raise SystemExit("Python headers are required to build Mercurial")
585 582 warning: line over 80 characters
586 583 setup.py:0:
587 584 > except:
588 585 warning: naked except clause
589 586 setup.py:0:
590 587 > # build_py), it will not find osutil & friends, thinking that those modules are
591 588 warning: line over 80 characters
592 589 setup.py:0:
593 590 > except:
594 591 warning: naked except clause
595 592 warning: naked except clause
596 593 setup.py:0:
597 594 > isironpython = platform.python_implementation().lower().find("ironpython") != -1
598 595 warning: line over 80 characters
599 596 setup.py:0:
600 597 > except:
601 598 warning: naked except clause
602 599 warning: naked except clause
603 600 warning: naked except clause
604 601 tests/autodiff.py:0:
605 602 > ui.write('data lost for: %s\n' % fn)
606 603 warning: unwrapped ui message
607 604 tests/run-tests.py:0:
608 605 > except:
609 606 warning: naked except clause
610 607 tests/test-commandserver.py:0:
611 608 > 'hooks.pre-identify=python:test-commandserver.hook', 'id'],
612 609 warning: line over 80 characters
613 610 tests/test-commandserver.py:0:
614 611 > # the cached repo local hgrc contains ui.foo=bar, so showconfig should show it
615 612 warning: line over 80 characters
616 613 tests/test-commandserver.py:0:
617 614 > print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***', data))
618 615 warning: line over 80 characters
619 616 tests/test-filecache.py:0:
620 617 > except:
621 618 warning: naked except clause
622 619 tests/test-filecache.py:0:
623 620 > if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'], 'cacheable']):
624 621 warning: line over 80 characters
625 622 tests/test-ui-color.py:0:
626 623 > testui.warn('warning\n')
627 624 warning: unwrapped ui message
628 625 tests/test-ui-color.py:0:
629 626 > testui.write('buffered\n')
630 627 warning: unwrapped ui message
631 628 tests/test-walkrepo.py:0:
632 629 > print "Found %d repositories when I should have found 2" % (len(reposet),)
633 630 warning: line over 80 characters
634 631 tests/test-walkrepo.py:0:
635 632 > print "Found %d repositories when I should have found 3" % (len(reposet),)
636 633 warning: line over 80 characters
@@ -1,1147 +1,1172 b''
1 1 $ "$TESTDIR/hghave" symlink unix-permissions serve || exit 80
2 2 $ USERCACHE=`pwd`/cache; export USERCACHE
3 3 $ mkdir -p ${USERCACHE}
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [extensions]
6 6 > largefiles=
7 7 > purge=
8 8 > rebase=
9 9 > transplant=
10 10 > [phases]
11 11 > publish=False
12 12 > [largefiles]
13 13 > minsize=2
14 14 > patterns=glob:**.dat
15 15 > usercache=${USERCACHE}
16 16 > [hooks]
17 17 > precommit=echo "Invoking status precommit hook"; hg status
18 18 > EOF
19 19
20 20 Create the repo with a couple of revisions of both large and normal
21 21 files, testing that status correctly shows largefiles and that summary output
22 22 is correct.
23 23
24 24 $ hg init a
25 25 $ cd a
26 26 $ mkdir sub
27 27 $ echo normal1 > normal1
28 28 $ echo normal2 > sub/normal2
29 29 $ echo large1 > large1
30 30 $ echo large2 > sub/large2
31 31 $ hg add normal1 sub/normal2
32 32 $ hg add --large large1 sub/large2
33 33 $ hg commit -m "add files"
34 34 Invoking status precommit hook
35 35 A large1
36 36 A normal1
37 37 A sub/large2
38 38 A sub/normal2
39 39 $ echo normal11 > normal1
40 40 $ echo normal22 > sub/normal2
41 41 $ echo large11 > large1
42 42 $ echo large22 > sub/large2
43 43 $ hg commit -m "edit files"
44 44 Invoking status precommit hook
45 45 M large1
46 46 M normal1
47 47 M sub/large2
48 48 M sub/normal2
49 49 $ hg sum --large
50 50 parent: 1:ce8896473775 tip
51 51 edit files
52 52 branch: default
53 53 commit: (clean)
54 54 update: (current)
55 55 largefiles: No remote repo
56 56
57 57 Commit preserved largefile contents.
58 58
59 59 $ cat normal1
60 60 normal11
61 61 $ cat large1
62 62 large11
63 63 $ cat sub/normal2
64 64 normal22
65 65 $ cat sub/large2
66 66 large22
67 67
68 68 Test status, subdir and unknown files
69 69
70 70 $ echo unknown > sub/unknown
71 71 $ hg st --all
72 72 ? sub/unknown
73 73 C large1
74 74 C normal1
75 75 C sub/large2
76 76 C sub/normal2
77 77 $ hg st --all sub
78 78 ? sub/unknown
79 79 C sub/large2
80 80 C sub/normal2
81 81 $ rm sub/unknown
82 82
83 83 Remove both largefiles and normal files.
84 84
85 85 $ hg remove normal1 large1
86 86 $ hg status large1
87 87 R large1
88 88 $ hg commit -m "remove files"
89 89 Invoking status precommit hook
90 90 R large1
91 91 R normal1
92 92 $ ls
93 93 sub
94 94 $ echo "testlargefile" > large1-test
95 95 $ hg add --large large1-test
96 96 $ hg st
97 97 A large1-test
98 98 $ hg rm large1-test
99 99 not removing large1-test: file has been marked for add (use forget to undo)
100 100 $ hg st
101 101 A large1-test
102 102 $ hg forget large1-test
103 103 $ hg st
104 104 ? large1-test
105 105 $ rm large1-test
106 106
107 107 Copy both largefiles and normal files (testing that status output is correct).
108 108
109 109 $ hg cp sub/normal2 normal1
110 110 $ hg cp sub/large2 large1
111 111 $ hg commit -m "copy files"
112 112 Invoking status precommit hook
113 113 A large1
114 114 A normal1
115 115 $ cat normal1
116 116 normal22
117 117 $ cat large1
118 118 large22
119 119
120 120 Test moving largefiles and verify that normal files are also unaffected.
121 121
122 122 $ hg mv normal1 normal3
123 123 $ hg mv large1 large3
124 124 $ hg mv sub/normal2 sub/normal4
125 125 $ hg mv sub/large2 sub/large4
126 126 $ hg commit -m "move files"
127 127 Invoking status precommit hook
128 128 A large3
129 129 A normal3
130 130 A sub/large4
131 131 A sub/normal4
132 132 R large1
133 133 R normal1
134 134 R sub/large2
135 135 R sub/normal2
136 136 $ cat normal3
137 137 normal22
138 138 $ cat large3
139 139 large22
140 140 $ cat sub/normal4
141 141 normal22
142 142 $ cat sub/large4
143 143 large22
144 144
145 145 Test display of largefiles in hgweb
146 146
147 147 $ hg serve -d -p $HGPORT --pid-file ../hg.pid
148 148 $ cat ../hg.pid >> $DAEMON_PIDS
149 149 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/tip/?style=raw'
150 150 200 Script output follows
151 151
152 152
153 153 drwxr-xr-x sub
154 154 -rw-r--r-- 41 large3
155 155 -rw-r--r-- 9 normal3
156 156
157 157
158 158 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/tip/sub/?style=raw'
159 159 200 Script output follows
160 160
161 161
162 162 -rw-r--r-- 41 large4
163 163 -rw-r--r-- 9 normal4
164 164
165 165
166 166 $ "$TESTDIR/killdaemons.py"
167 167
168 168 Test archiving the various revisions. These hit corner cases known with
169 169 archiving.
170 170
171 171 $ hg archive -r 0 ../archive0
172 172 $ hg archive -r 1 ../archive1
173 173 $ hg archive -r 2 ../archive2
174 174 $ hg archive -r 3 ../archive3
175 175 $ hg archive -r 4 ../archive4
176 176 $ cd ../archive0
177 177 $ cat normal1
178 178 normal1
179 179 $ cat large1
180 180 large1
181 181 $ cat sub/normal2
182 182 normal2
183 183 $ cat sub/large2
184 184 large2
185 185 $ cd ../archive1
186 186 $ cat normal1
187 187 normal11
188 188 $ cat large1
189 189 large11
190 190 $ cat sub/normal2
191 191 normal22
192 192 $ cat sub/large2
193 193 large22
194 194 $ cd ../archive2
195 195 $ ls
196 196 sub
197 197 $ cat sub/normal2
198 198 normal22
199 199 $ cat sub/large2
200 200 large22
201 201 $ cd ../archive3
202 202 $ cat normal1
203 203 normal22
204 204 $ cat large1
205 205 large22
206 206 $ cat sub/normal2
207 207 normal22
208 208 $ cat sub/large2
209 209 large22
210 210 $ cd ../archive4
211 211 $ cat normal3
212 212 normal22
213 213 $ cat large3
214 214 large22
215 215 $ cat sub/normal4
216 216 normal22
217 217 $ cat sub/large4
218 218 large22
219 219
220 220 Commit corner case: specify files to commit.
221 221
222 222 $ cd ../a
223 223 $ echo normal3 > normal3
224 224 $ echo large3 > large3
225 225 $ echo normal4 > sub/normal4
226 226 $ echo large4 > sub/large4
227 227 $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again"
228 228 Invoking status precommit hook
229 229 M large3
230 230 M normal3
231 231 M sub/large4
232 232 M sub/normal4
233 233 $ cat normal3
234 234 normal3
235 235 $ cat large3
236 236 large3
237 237 $ cat sub/normal4
238 238 normal4
239 239 $ cat sub/large4
240 240 large4
241 241
242 242 One more commit corner case: commit from a subdirectory.
243 243
244 244 $ cd ../a
245 245 $ echo normal33 > normal3
246 246 $ echo large33 > large3
247 247 $ echo normal44 > sub/normal4
248 248 $ echo large44 > sub/large4
249 249 $ cd sub
250 250 $ hg commit -m "edit files yet again"
251 251 Invoking status precommit hook
252 252 M large3
253 253 M normal3
254 254 M sub/large4
255 255 M sub/normal4
256 256 $ cat ../normal3
257 257 normal33
258 258 $ cat ../large3
259 259 large33
260 260 $ cat normal4
261 261 normal44
262 262 $ cat large4
263 263 large44
264 264
265 265 Committing standins is not allowed.
266 266
267 267 $ cd ..
268 268 $ echo large3 > large3
269 269 $ hg commit .hglf/large3 -m "try to commit standin"
270 270 abort: file ".hglf/large3" is a largefile standin
271 271 (commit the largefile itself instead)
272 272 [255]
273 273
274 274 Corner cases for adding largefiles.
275 275
276 276 $ echo large5 > large5
277 277 $ hg add --large large5
278 278 $ hg add --large large5
279 279 large5 already a largefile
280 280 $ mkdir sub2
281 281 $ echo large6 > sub2/large6
282 282 $ echo large7 > sub2/large7
283 283 $ hg add --large sub2
284 284 adding sub2/large6 as a largefile (glob)
285 285 adding sub2/large7 as a largefile (glob)
286 286 $ hg st
287 287 M large3
288 288 A large5
289 289 A sub2/large6
290 290 A sub2/large7
291 291
292 292 Test "hg status" with combination of 'file pattern' and 'directory
293 293 pattern' for largefiles:
294 294
295 295 $ hg status sub2/large6 sub2
296 296 A sub2/large6
297 297 A sub2/large7
298 298
299 299 Config settings (pattern **.dat, minsize 2 MB) are respected.
300 300
301 301 $ echo testdata > test.dat
302 302 $ dd bs=1k count=2k if=/dev/zero of=reallylarge > /dev/null 2> /dev/null
303 303 $ hg add
304 304 adding reallylarge as a largefile
305 305 adding test.dat as a largefile
306 306
307 307 Test that minsize and --lfsize handle float values;
308 308 also tests that --lfsize overrides largefiles.minsize.
309 309 (0.250 MB = 256 kB = 262144 B)
310 310
311 311 $ dd if=/dev/zero of=ratherlarge bs=1024 count=256 > /dev/null 2> /dev/null
312 312 $ dd if=/dev/zero of=medium bs=1024 count=128 > /dev/null 2> /dev/null
313 313 $ hg --config largefiles.minsize=.25 add
314 314 adding ratherlarge as a largefile
315 315 adding medium
316 316 $ hg forget medium
317 317 $ hg --config largefiles.minsize=.25 add --lfsize=.125
318 318 adding medium as a largefile
319 319 $ dd if=/dev/zero of=notlarge bs=1024 count=127 > /dev/null 2> /dev/null
320 320 $ hg --config largefiles.minsize=.25 add --lfsize=.125
321 321 adding notlarge
322 322 $ hg forget notlarge
323 323
324 324 Test forget on largefiles.
325 325
326 326 $ hg forget large3 large5 test.dat reallylarge ratherlarge medium
327 327 $ hg commit -m "add/edit more largefiles"
328 328 Invoking status precommit hook
329 329 A sub2/large6
330 330 A sub2/large7
331 331 R large3
332 332 ? large5
333 333 ? medium
334 334 ? notlarge
335 335 ? ratherlarge
336 336 ? reallylarge
337 337 ? test.dat
338 338 $ hg st
339 339 ? large3
340 340 ? large5
341 341 ? medium
342 342 ? notlarge
343 343 ? ratherlarge
344 344 ? reallylarge
345 345 ? test.dat
346 346
347 347 Purge with largefiles: verify that largefiles are still in the working
348 348 dir after a purge.
349 349
350 350 $ hg purge --all
351 351 $ cat sub/large4
352 352 large44
353 353 $ cat sub2/large6
354 354 large6
355 355 $ cat sub2/large7
356 356 large7
357 357
358 358 Test addremove: verify that files that should be added as largfiles are added as
359 359 such and that already-existing largfiles are not added as normal files by
360 360 accident.
361 361
362 362 $ rm normal3
363 363 $ rm sub/large4
364 364 $ echo "testing addremove with patterns" > testaddremove.dat
365 365 $ echo "normaladdremove" > normaladdremove
366 366 $ hg addremove
367 367 removing sub/large4
368 368 adding testaddremove.dat as a largefile
369 369 removing normal3
370 370 adding normaladdremove
371 371
372 372 Clone a largefiles repo.
373 373
374 374 $ hg clone . ../b
375 375 updating to branch default
376 376 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 377 getting changed largefiles
378 378 3 largefiles updated, 0 removed
379 379 $ cd ../b
380 380 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
381 381 7:daea875e9014 add/edit more largefiles
382 382 6:4355d653f84f edit files yet again
383 383 5:9d5af5072dbd edit files again
384 384 4:74c02385b94c move files
385 385 3:9e8fbc4bce62 copy files
386 386 2:51a0ae4d5864 remove files
387 387 1:ce8896473775 edit files
388 388 0:30d30fe6a5be add files
389 389 $ cat normal3
390 390 normal33
391 391 $ cat sub/normal4
392 392 normal44
393 393 $ cat sub/large4
394 394 large44
395 395 $ cat sub2/large6
396 396 large6
397 397 $ cat sub2/large7
398 398 large7
399 399 $ cd ..
400 400 $ hg clone a -r 3 c
401 401 adding changesets
402 402 adding manifests
403 403 adding file changes
404 404 added 4 changesets with 10 changes to 4 files
405 405 updating to branch default
406 406 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 407 getting changed largefiles
408 408 2 largefiles updated, 0 removed
409 409 $ cd c
410 410 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
411 411 3:9e8fbc4bce62 copy files
412 412 2:51a0ae4d5864 remove files
413 413 1:ce8896473775 edit files
414 414 0:30d30fe6a5be add files
415 415 $ cat normal1
416 416 normal22
417 417 $ cat large1
418 418 large22
419 419 $ cat sub/normal2
420 420 normal22
421 421 $ cat sub/large2
422 422 large22
423 423
424 424 Old revisions of a clone have correct largefiles content (this also
425 425 tests update).
426 426
427 427 $ hg update -r 1
428 428 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
429 429 getting changed largefiles
430 430 1 largefiles updated, 0 removed
431 431 $ cat large1
432 432 large11
433 433 $ cat sub/large2
434 434 large22
435 435
436 436 Rebasing between two repositories does not revert largefiles to old
437 437 revisions (this was a very bad bug that took a lot of work to fix).
438 438
439 439 $ cd ..
440 440 $ hg clone a d
441 441 updating to branch default
442 442 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 443 getting changed largefiles
444 444 3 largefiles updated, 0 removed
445 445 $ cd b
446 446 $ echo large4-modified > sub/large4
447 447 $ echo normal3-modified > normal3
448 448 $ hg commit -m "modify normal file and largefile in repo b"
449 449 Invoking status precommit hook
450 450 M normal3
451 451 M sub/large4
452 452 $ cd ../d
453 453 $ echo large6-modified > sub2/large6
454 454 $ echo normal4-modified > sub/normal4
455 455 $ hg commit -m "modify normal file largefile in repo d"
456 456 Invoking status precommit hook
457 457 M sub/normal4
458 458 M sub2/large6
459 459 $ cd ..
460 460 $ hg clone d e
461 461 updating to branch default
462 462 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 463 getting changed largefiles
464 464 3 largefiles updated, 0 removed
465 465 $ cd d
466 466 $ hg pull --rebase ../b
467 467 pulling from ../b
468 468 searching for changes
469 469 adding changesets
470 470 adding manifests
471 471 adding file changes
472 472 added 1 changesets with 2 changes to 2 files (+1 heads)
473 473 Invoking status precommit hook
474 474 M sub/normal4
475 475 M sub2/large6
476 476 saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-backup.hg
477 477 nothing to rebase
478 478 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
479 479 9:598410d3eb9a modify normal file largefile in repo d
480 480 8:a381d2c8c80e modify normal file and largefile in repo b
481 481 7:daea875e9014 add/edit more largefiles
482 482 6:4355d653f84f edit files yet again
483 483 5:9d5af5072dbd edit files again
484 484 4:74c02385b94c move files
485 485 3:9e8fbc4bce62 copy files
486 486 2:51a0ae4d5864 remove files
487 487 1:ce8896473775 edit files
488 488 0:30d30fe6a5be add files
489 489 $ cat normal3
490 490 normal3-modified
491 491 $ cat sub/normal4
492 492 normal4-modified
493 493 $ cat sub/large4
494 494 large4-modified
495 495 $ cat sub2/large6
496 496 large6-modified
497 497 $ cat sub2/large7
498 498 large7
499 499 $ cd ../e
500 500 $ hg pull ../b
501 501 pulling from ../b
502 502 searching for changes
503 503 adding changesets
504 504 adding manifests
505 505 adding file changes
506 506 added 1 changesets with 2 changes to 2 files (+1 heads)
507 507 (run 'hg heads' to see heads, 'hg merge' to merge)
508 508 caching new largefiles
509 509 0 largefiles cached
510 510 $ hg rebase
511 511 Invoking status precommit hook
512 512 M sub/normal4
513 513 M sub2/large6
514 514 saved backup bundle to $TESTTMP/e/.hg/strip-backup/f574fb32bb45-backup.hg
515 515 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
516 516 9:598410d3eb9a modify normal file largefile in repo d
517 517 8:a381d2c8c80e modify normal file and largefile in repo b
518 518 7:daea875e9014 add/edit more largefiles
519 519 6:4355d653f84f edit files yet again
520 520 5:9d5af5072dbd edit files again
521 521 4:74c02385b94c move files
522 522 3:9e8fbc4bce62 copy files
523 523 2:51a0ae4d5864 remove files
524 524 1:ce8896473775 edit files
525 525 0:30d30fe6a5be add files
526 526 $ cat normal3
527 527 normal3-modified
528 528 $ cat sub/normal4
529 529 normal4-modified
530 530 $ cat sub/large4
531 531 large4-modified
532 532 $ cat sub2/large6
533 533 large6-modified
534 534 $ cat sub2/large7
535 535 large7
536 536
537 537 Rollback on largefiles.
538 538
539 539 $ echo large4-modified-again > sub/large4
540 540 $ hg commit -m "Modify large4 again"
541 541 Invoking status precommit hook
542 542 M sub/large4
543 543 $ hg rollback
544 544 repository tip rolled back to revision 9 (undo commit)
545 545 working directory now based on revision 9
546 546 $ hg st
547 547 M sub/large4
548 548 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
549 549 9:598410d3eb9a modify normal file largefile in repo d
550 550 8:a381d2c8c80e modify normal file and largefile in repo b
551 551 7:daea875e9014 add/edit more largefiles
552 552 6:4355d653f84f edit files yet again
553 553 5:9d5af5072dbd edit files again
554 554 4:74c02385b94c move files
555 555 3:9e8fbc4bce62 copy files
556 556 2:51a0ae4d5864 remove files
557 557 1:ce8896473775 edit files
558 558 0:30d30fe6a5be add files
559 559 $ cat sub/large4
560 560 large4-modified-again
561 561
562 562 "update --check" refuses to update with uncommitted changes.
563 563 $ hg update --check 8
564 564 abort: uncommitted local changes
565 565 [255]
566 566
567 567 "update --clean" leaves correct largefiles in working copy.
568 568
569 569 $ hg update --clean
570 570 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 571 getting changed largefiles
572 572 1 largefiles updated, 0 removed
573 573 $ cat normal3
574 574 normal3-modified
575 575 $ cat sub/normal4
576 576 normal4-modified
577 577 $ cat sub/large4
578 578 large4-modified
579 579 $ cat sub2/large6
580 580 large6-modified
581 581 $ cat sub2/large7
582 582 large7
583 583
584 584 Now "update check" is happy.
585 585 $ hg update --check 8
586 586 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 587 getting changed largefiles
588 588 1 largefiles updated, 0 removed
589 589 $ hg update --check
590 590 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
591 591 getting changed largefiles
592 592 1 largefiles updated, 0 removed
593 593
594 594 Test removing empty largefiles directories on update
595 595 $ test -d sub2 && echo "sub2 exists"
596 596 sub2 exists
597 597 $ hg update -q null
598 598 $ test -d sub2 && echo "error: sub2 should not exist anymore"
599 599 [1]
600 600 $ hg update -q
601 601
602 602 Test hg remove removes empty largefiles directories
603 603 $ test -d sub2 && echo "sub2 exists"
604 604 sub2 exists
605 605 $ hg remove sub2/*
606 606 $ test -d sub2 && echo "error: sub2 should not exist anymore"
607 607 [1]
608 608 $ hg revert sub2/large6 sub2/large7
609 609
610 610 "revert" works on largefiles (and normal files too).
611 611 $ echo hack3 >> normal3
612 612 $ echo hack4 >> sub/normal4
613 613 $ echo hack4 >> sub/large4
614 614 $ rm sub2/large6
615 615 $ hg revert sub2/large6
616 616 $ hg rm sub2/large6
617 617 $ echo new >> sub2/large8
618 618 $ hg add --large sub2/large8
619 619 # XXX we don't really want to report that we're reverting the standin;
620 620 # that's just an implementation detail. But I don't see an obvious fix. ;-(
621 621 $ hg revert sub
622 622 reverting .hglf/sub/large4 (glob)
623 623 reverting sub/normal4 (glob)
624 624 $ hg status
625 625 M normal3
626 626 A sub2/large8
627 627 R sub2/large6
628 628 ? sub/large4.orig
629 629 ? sub/normal4.orig
630 630 $ cat sub/normal4
631 631 normal4-modified
632 632 $ cat sub/large4
633 633 large4-modified
634 634 $ hg revert -a --no-backup
635 635 undeleting .hglf/sub2/large6 (glob)
636 636 forgetting .hglf/sub2/large8 (glob)
637 637 reverting normal3
638 638 $ hg status
639 639 ? sub/large4.orig
640 640 ? sub/normal4.orig
641 641 ? sub2/large8
642 642 $ cat normal3
643 643 normal3-modified
644 644 $ cat sub2/large6
645 645 large6-modified
646 646 $ rm sub/*.orig sub2/large8
647 647
648 648 revert some files to an older revision
649 649 $ hg revert --no-backup -r 8 sub2
650 650 reverting .hglf/sub2/large6 (glob)
651 651 $ cat sub2/large6
652 652 large6
653 653 $ hg revert --no-backup sub2
654 654 reverting .hglf/sub2/large6 (glob)
655 655 $ hg status
656 656
657 657 "verify --large" actually verifies largefiles
658 658
659 659 $ hg verify --large
660 660 checking changesets
661 661 checking manifests
662 662 crosschecking files in changesets and manifests
663 663 checking files
664 664 10 files, 10 changesets, 28 total revisions
665 665 searching 1 changesets for largefiles
666 666 verified existence of 3 revisions of 3 largefiles
667 667
668 668 Merging does not revert to old versions of largefiles and also check
669 669 that merging after having pulled from a non-default remote works
670 670 correctly.
671 671
672 672 $ cd ..
673 673 $ hg clone -r 7 e temp
674 674 adding changesets
675 675 adding manifests
676 676 adding file changes
677 677 added 8 changesets with 24 changes to 10 files
678 678 updating to branch default
679 679 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
680 680 getting changed largefiles
681 681 3 largefiles updated, 0 removed
682 682 $ hg clone temp f
683 683 updating to branch default
684 684 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
685 685 getting changed largefiles
686 686 3 largefiles updated, 0 removed
687 687 # Delete the largefiles in the largefiles system cache so that we have an
688 688 # opportunity to test that caching after a pull works.
689 689 $ rm ${USERCACHE}/*
690 690 $ cd f
691 691 $ echo "large4-merge-test" > sub/large4
692 692 $ hg commit -m "Modify large4 to test merge"
693 693 Invoking status precommit hook
694 694 M sub/large4
695 695 $ hg pull ../e
696 696 pulling from ../e
697 697 searching for changes
698 698 adding changesets
699 699 adding manifests
700 700 adding file changes
701 701 added 2 changesets with 4 changes to 4 files (+1 heads)
702 702 (run 'hg heads' to see heads, 'hg merge' to merge)
703 703 caching new largefiles
704 704 2 largefiles cached
705 705 $ hg merge
706 706 merging sub/large4
707 707 largefile sub/large4 has a merge conflict
708 708 keep (l)ocal or take (o)ther? l
709 709 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
710 710 (branch merge, don't forget to commit)
711 711 getting changed largefiles
712 712 1 largefiles updated, 0 removed
713 713 $ hg commit -m "Merge repos e and f"
714 714 Invoking status precommit hook
715 715 M normal3
716 716 M sub/normal4
717 717 M sub2/large6
718 718 $ cat normal3
719 719 normal3-modified
720 720 $ cat sub/normal4
721 721 normal4-modified
722 722 $ cat sub/large4
723 723 large4-merge-test
724 724 $ cat sub2/large6
725 725 large6-modified
726 726 $ cat sub2/large7
727 727 large7
728 728
729 729 Test status after merging with a branch that introduces a new largefile:
730 730
731 731 $ echo large > large
732 732 $ hg add --large large
733 733 $ hg commit -m 'add largefile'
734 734 Invoking status precommit hook
735 735 A large
736 736 $ hg update -q ".^"
737 737 $ echo change >> normal3
738 738 $ hg commit -m 'some change'
739 739 Invoking status precommit hook
740 740 M normal3
741 741 created new head
742 742 $ hg merge
743 743 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
744 744 (branch merge, don't forget to commit)
745 745 getting changed largefiles
746 746 1 largefiles updated, 0 removed
747 747 $ hg status
748 748 M large
749 749
750 750 Test that a normal file and a largefile with the same name and path cannot
751 751 coexist.
752 752
753 753 $ rm sub2/large7
754 754 $ echo "largeasnormal" > sub2/large7
755 755 $ hg add sub2/large7
756 756 sub2/large7 already a largefile
757 757
758 758 Test that transplanting a largefile change works correctly.
759 759
760 760 $ cd ..
761 761 $ hg clone -r 8 d g
762 762 adding changesets
763 763 adding manifests
764 764 adding file changes
765 765 added 9 changesets with 26 changes to 10 files
766 766 updating to branch default
767 767 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
768 768 getting changed largefiles
769 769 3 largefiles updated, 0 removed
770 770 $ cd g
771 771 $ hg transplant -s ../d 598410d3eb9a
772 772 searching for changes
773 773 searching for changes
774 774 adding changesets
775 775 adding manifests
776 776 adding file changes
777 777 added 1 changesets with 2 changes to 2 files
778 778 getting changed largefiles
779 779 1 largefiles updated, 0 removed
780 780 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
781 781 9:598410d3eb9a modify normal file largefile in repo d
782 782 8:a381d2c8c80e modify normal file and largefile in repo b
783 783 7:daea875e9014 add/edit more largefiles
784 784 6:4355d653f84f edit files yet again
785 785 5:9d5af5072dbd edit files again
786 786 4:74c02385b94c move files
787 787 3:9e8fbc4bce62 copy files
788 788 2:51a0ae4d5864 remove files
789 789 1:ce8896473775 edit files
790 790 0:30d30fe6a5be add files
791 791 $ cat normal3
792 792 normal3-modified
793 793 $ cat sub/normal4
794 794 normal4-modified
795 795 $ cat sub/large4
796 796 large4-modified
797 797 $ cat sub2/large6
798 798 large6-modified
799 799 $ cat sub2/large7
800 800 large7
801 801
802 802 Cat a largefile
803 803 $ hg cat normal3
804 804 normal3-modified
805 805 $ hg cat sub/large4
806 806 large4-modified
807 807 $ rm ${USERCACHE}/*
808 808 $ hg cat -r a381d2c8c80e -o cat.out sub/large4
809 809 $ cat cat.out
810 810 large4-modified
811 811 $ rm cat.out
812 812 $ hg cat -r a381d2c8c80e normal3
813 813 normal3-modified
814 814
815 815 Test that renaming a largefile results in correct output for status
816 816
817 817 $ hg rename sub/large4 large4-renamed
818 818 $ hg commit -m "test rename output"
819 819 Invoking status precommit hook
820 820 A large4-renamed
821 821 R sub/large4
822 822 $ cat large4-renamed
823 823 large4-modified
824 824 $ cd sub2
825 825 $ hg rename large6 large6-renamed
826 826 $ hg st
827 827 A sub2/large6-renamed
828 828 R sub2/large6
829 829 $ cd ..
830 830
831 831 Test --normal flag
832 832
833 833 $ dd if=/dev/zero bs=2k count=11k > new-largefile 2> /dev/null
834 834 $ hg add --normal --large new-largefile
835 835 abort: --normal cannot be used with --large
836 836 [255]
837 837 $ hg add --normal new-largefile
838 838 new-largefile: up to 69 MB of RAM may be required to manage this file
839 839 (use 'hg revert new-largefile' to cancel the pending addition)
840 840 $ cd ..
841 841
842 842 vanilla clients not locked out from largefiles servers on vanilla repos
843 843 $ mkdir r1
844 844 $ cd r1
845 845 $ hg init
846 846 $ echo c1 > f1
847 847 $ hg add f1
848 848 $ hg commit -m "m1"
849 849 Invoking status precommit hook
850 850 A f1
851 851 $ cd ..
852 852 $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid
853 853 $ cat hg.pid >> $DAEMON_PIDS
854 854 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2
855 855 requesting all changes
856 856 adding changesets
857 857 adding manifests
858 858 adding file changes
859 859 added 1 changesets with 1 changes to 1 files
860 860 updating to branch default
861 861 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
862 862
863 863 largefiles clients still work with vanilla servers
864 864 $ hg --config extensions.largefiles=! serve -R r1 -d -p $HGPORT1 --pid-file hg.pid
865 865 $ cat hg.pid >> $DAEMON_PIDS
866 866 $ hg clone http://localhost:$HGPORT1 r3
867 867 requesting all changes
868 868 adding changesets
869 869 adding manifests
870 870 adding file changes
871 871 added 1 changesets with 1 changes to 1 files
872 872 updating to branch default
873 873 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
874 874
875 875 vanilla clients locked out from largefiles http repos
876 876 $ mkdir r4
877 877 $ cd r4
878 878 $ hg init
879 879 $ echo c1 > f1
880 880 $ hg add --large f1
881 881 $ hg commit -m "m1"
882 882 Invoking status precommit hook
883 883 A f1
884 884 $ cd ..
885 885 $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid
886 886 $ cat hg.pid >> $DAEMON_PIDS
887 887 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5
888 888 abort: remote error:
889 889
890 890 This repository uses the largefiles extension.
891 891
892 892 Please enable it in your Mercurial config file.
893 893 [255]
894 894
895 895 used all HGPORTs, kill all daemons
896 896 $ "$TESTDIR/killdaemons.py"
897 897
898 898 vanilla clients locked out from largefiles ssh repos
899 899 $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
900 900 abort: remote error:
901 901
902 902 This repository uses the largefiles extension.
903 903
904 904 Please enable it in your Mercurial config file.
905 905 [255]
906 906
907 907 largefiles clients refuse to push largefiles repos to vanilla servers
908 908 $ mkdir r6
909 909 $ cd r6
910 910 $ hg init
911 911 $ echo c1 > f1
912 912 $ hg add f1
913 913 $ hg commit -m "m1"
914 914 Invoking status precommit hook
915 915 A f1
916 916 $ cat >> .hg/hgrc <<!
917 917 > [web]
918 918 > push_ssl = false
919 919 > allow_push = *
920 920 > !
921 921 $ cd ..
922 922 $ hg clone r6 r7
923 923 updating to branch default
924 924 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
925 925 $ cd r7
926 926 $ echo c2 > f2
927 927 $ hg add --large f2
928 928 $ hg commit -m "m2"
929 929 Invoking status precommit hook
930 930 A f2
931 931 $ hg --config extensions.largefiles=! -R ../r6 serve -d -p $HGPORT --pid-file ../hg.pid
932 932 $ cat ../hg.pid >> $DAEMON_PIDS
933 933 $ hg push http://localhost:$HGPORT
934 934 pushing to http://localhost:$HGPORT/
935 935 searching for changes
936 936 abort: http://localhost:$HGPORT/ does not appear to be a largefile store
937 937 [255]
938 938 $ cd ..
939 939
940 940 putlfile errors are shown (issue3123)
941 941 Corrupt the cached largefile in r7
942 942 $ echo corruption > $USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8
943 943 $ hg init empty
944 944 $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \
945 945 > --config 'web.allow_push=*' --config web.push_ssl=False
946 946 $ cat hg.pid >> $DAEMON_PIDS
947 947 $ hg push -R r7 http://localhost:$HGPORT1
948 948 pushing to http://localhost:$HGPORT1/
949 949 searching for changes
950 950 remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash
951 951 abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/
952 952 [255]
953 953 $ rm -rf empty
954 954
955 Push a largefiles repository to a served empty repository
956 $ hg init r8
957 $ echo c3 > r8/f1
958 $ hg add --large r8/f1 -R r8
959 $ hg commit -m "m1" -R r8
960 Invoking status precommit hook
961 A f1
962 $ hg init empty
963 $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \
964 > --config 'web.allow_push=*' --config web.push_ssl=False
965 $ cat hg.pid >> $DAEMON_PIDS
966 $ rm ${USERCACHE}/*
967 $ hg push -R r8 http://localhost:$HGPORT2
968 pushing to http://localhost:$HGPORT2/
969 searching for changes
970 searching for changes
971 remote: adding changesets
972 remote: adding manifests
973 remote: adding file changes
974 remote: added 1 changesets with 1 changes to 1 files
975 $ rm -rf empty
976
977 used all HGPORTs, kill all daemons
978 $ "$TESTDIR/killdaemons.py"
979
955 980 Clone a local repository owned by another user
956 981 We have to simulate that here by setting $HOME and removing write permissions
957 982 $ ORIGHOME="$HOME"
958 983 $ mkdir alice
959 984 $ HOME="`pwd`/alice"
960 985 $ cd alice
961 986 $ hg init pubrepo
962 987 $ cd pubrepo
963 988 $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null
964 989 $ hg add --large a-large-file
965 990 $ hg commit -m "Add a large file"
966 991 Invoking status precommit hook
967 992 A a-large-file
968 993 $ cd ..
969 994 $ chmod -R a-w pubrepo
970 995 $ cd ..
971 996 $ mkdir bob
972 997 $ HOME="`pwd`/bob"
973 998 $ cd bob
974 999 $ hg clone --pull ../alice/pubrepo pubrepo
975 1000 requesting all changes
976 1001 adding changesets
977 1002 adding manifests
978 1003 adding file changes
979 1004 added 1 changesets with 1 changes to 1 files
980 1005 updating to branch default
981 1006 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
982 1007 getting changed largefiles
983 1008 1 largefiles updated, 0 removed
984 1009 $ cd ..
985 1010 $ chmod -R u+w alice/pubrepo
986 1011 $ HOME="$ORIGHOME"
987 1012
988 1013 Symlink to a large largefile should behave the same as a symlink to a normal file
989 1014 $ hg init largesymlink
990 1015 $ cd largesymlink
991 1016 $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null
992 1017 $ hg add --large largefile
993 1018 $ hg commit -m "commit a large file"
994 1019 Invoking status precommit hook
995 1020 A largefile
996 1021 $ ln -s largefile largelink
997 1022 $ hg add largelink
998 1023 $ hg commit -m "commit a large symlink"
999 1024 Invoking status precommit hook
1000 1025 A largelink
1001 1026 $ rm -f largelink
1002 1027 $ hg up >/dev/null
1003 1028 $ test -f largelink
1004 1029 [1]
1005 1030 $ test -L largelink
1006 1031 [1]
1007 1032 $ rm -f largelink # make next part of the test independent of the previous
1008 1033 $ hg up -C >/dev/null
1009 1034 $ test -f largelink
1010 1035 $ test -L largelink
1011 1036 $ cd ..
1012 1037
1013 1038 test for pattern matching on 'hg status':
1014 1039 to boost performance, largefiles checks whether specified patterns are
1015 1040 related to largefiles in working directory (NOT to STANDIN) or not.
1016 1041
1017 1042 $ hg init statusmatch
1018 1043 $ cd statusmatch
1019 1044
1020 1045 $ mkdir -p a/b/c/d
1021 1046 $ echo normal > a/b/c/d/e.normal.txt
1022 1047 $ hg add a/b/c/d/e.normal.txt
1023 1048 $ echo large > a/b/c/d/e.large.txt
1024 1049 $ hg add --large a/b/c/d/e.large.txt
1025 1050 $ mkdir -p a/b/c/x
1026 1051 $ echo normal > a/b/c/x/y.normal.txt
1027 1052 $ hg add a/b/c/x/y.normal.txt
1028 1053 $ hg commit -m 'add files'
1029 1054 Invoking status precommit hook
1030 1055 A a/b/c/d/e.large.txt
1031 1056 A a/b/c/d/e.normal.txt
1032 1057 A a/b/c/x/y.normal.txt
1033 1058
1034 1059 (1) no pattern: no performance boost
1035 1060 $ hg status -A
1036 1061 C a/b/c/d/e.large.txt
1037 1062 C a/b/c/d/e.normal.txt
1038 1063 C a/b/c/x/y.normal.txt
1039 1064
1040 1065 (2) pattern not related to largefiles: performance boost
1041 1066 $ hg status -A a/b/c/x
1042 1067 C a/b/c/x/y.normal.txt
1043 1068
1044 1069 (3) pattern related to largefiles: no performance boost
1045 1070 $ hg status -A a/b/c/d
1046 1071 C a/b/c/d/e.large.txt
1047 1072 C a/b/c/d/e.normal.txt
1048 1073
1049 1074 (4) pattern related to STANDIN (not to largefiles): performance boost
1050 1075 $ hg status -A .hglf/a
1051 1076 C .hglf/a/b/c/d/e.large.txt
1052 1077
1053 1078 (5) mixed case: no performance boost
1054 1079 $ hg status -A a/b/c/x a/b/c/d
1055 1080 C a/b/c/d/e.large.txt
1056 1081 C a/b/c/d/e.normal.txt
1057 1082 C a/b/c/x/y.normal.txt
1058 1083
1059 1084 verify that largefiles doesn't break filesets
1060 1085
1061 1086 $ hg log --rev . --exclude "set:binary()"
1062 1087 changeset: 0:41bd42f10efa
1063 1088 tag: tip
1064 1089 user: test
1065 1090 date: Thu Jan 01 00:00:00 1970 +0000
1066 1091 summary: add files
1067 1092
1068 1093 verify that large files in subrepos handled properly
1069 1094 $ hg init subrepo
1070 1095 $ echo "subrepo = subrepo" > .hgsub
1071 1096 $ hg add .hgsub
1072 1097 $ hg ci -m "add subrepo"
1073 1098 Invoking status precommit hook
1074 1099 A .hgsub
1075 1100 ? .hgsubstate
1076 1101 $ echo "rev 1" > subrepo/large.txt
1077 1102 $ hg -R subrepo add --large subrepo/large.txt
1078 1103 $ hg sum
1079 1104 parent: 1:8ee150ea2e9c tip
1080 1105 add subrepo
1081 1106 branch: default
1082 1107 commit: 1 subrepos
1083 1108 update: (current)
1084 1109 $ hg st
1085 1110 $ hg st -S
1086 1111 A subrepo/large.txt
1087 1112 $ hg ci -S -m "commit top repo"
1088 1113 committing subrepository subrepo
1089 1114 Invoking status precommit hook
1090 1115 A large.txt
1091 1116 Invoking status precommit hook
1092 1117 M .hgsubstate
1093 1118 # No differences
1094 1119 $ hg st -S
1095 1120 $ hg sum
1096 1121 parent: 2:ce4cd0c527a6 tip
1097 1122 commit top repo
1098 1123 branch: default
1099 1124 commit: (clean)
1100 1125 update: (current)
1101 1126 $ echo "rev 2" > subrepo/large.txt
1102 1127 $ hg st -S
1103 1128 M subrepo/large.txt
1104 1129 $ hg sum
1105 1130 parent: 2:ce4cd0c527a6 tip
1106 1131 commit top repo
1107 1132 branch: default
1108 1133 commit: 1 subrepos
1109 1134 update: (current)
1110 1135 $ hg ci -m "this commit should fail without -S"
1111 1136 abort: uncommitted changes in subrepo subrepo
1112 1137 (use --subrepos for recursive commit)
1113 1138 [255]
1114 1139
1115 1140 Add a normal file to the subrepo, then test archiving
1116 1141
1117 1142 $ echo 'normal file' > subrepo/normal.txt
1118 1143 $ hg -R subrepo add subrepo/normal.txt
1119 1144
1120 1145 Lock in subrepo, otherwise the change isn't archived
1121 1146
1122 1147 $ hg ci -S -m "add normal file to top level"
1123 1148 committing subrepository subrepo
1124 1149 Invoking status precommit hook
1125 1150 M large.txt
1126 1151 A normal.txt
1127 1152 Invoking status precommit hook
1128 1153 M .hgsubstate
1129 1154 $ hg archive -S lf_subrepo_archive
1130 1155 $ find lf_subrepo_archive | sort
1131 1156 lf_subrepo_archive
1132 1157 lf_subrepo_archive/.hg_archival.txt
1133 1158 lf_subrepo_archive/.hgsub
1134 1159 lf_subrepo_archive/.hgsubstate
1135 1160 lf_subrepo_archive/a
1136 1161 lf_subrepo_archive/a/b
1137 1162 lf_subrepo_archive/a/b/c
1138 1163 lf_subrepo_archive/a/b/c/d
1139 1164 lf_subrepo_archive/a/b/c/d/e.large.txt
1140 1165 lf_subrepo_archive/a/b/c/d/e.normal.txt
1141 1166 lf_subrepo_archive/a/b/c/x
1142 1167 lf_subrepo_archive/a/b/c/x/y.normal.txt
1143 1168 lf_subrepo_archive/subrepo
1144 1169 lf_subrepo_archive/subrepo/large.txt
1145 1170 lf_subrepo_archive/subrepo/normal.txt
1146 1171
1147 1172 $ cd ..
@@ -1,53 +1,69 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3 $ hg init subrepo
4 4 $ echo a > subrepo/a
5 5 $ hg -R subrepo ci -Am adda
6 6 adding a
7 7 $ echo 'subrepo = subrepo' > .hgsub
8 8 $ hg ci -Am addsubrepo
9 9 adding .hgsub
10 10 $ echo b > subrepo/b
11 11 $ hg -R subrepo ci -Am addb
12 12 adding b
13 13 $ hg ci -m updatedsub
14 14
15 ignore blanklines in .hgsubstate
16
17 >>> file('.hgsubstate', 'wb').write('\n\n \t \n \n')
18 $ hg st --subrepos
19 M .hgsubstate
20 $ hg revert -qC .hgsubstate
21
22 abort more gracefully on .hgsubstate parsing error
23
24 $ cp .hgsubstate .hgsubstate.old
25 >>> file('.hgsubstate', 'wb').write('\ninvalid')
26 $ hg st --subrepos
27 abort: invalid subrepository revision specifier in .hgsubstate line 2
28 [255]
29 $ mv .hgsubstate.old .hgsubstate
30
15 31 delete .hgsub and revert it
16 32
17 33 $ rm .hgsub
18 34 $ hg revert .hgsub
19 35 warning: subrepo spec file .hgsub not found
20 36 warning: subrepo spec file .hgsub not found
21 37
22 38 delete .hgsubstate and revert it
23 39
24 40 $ rm .hgsubstate
25 41 $ hg revert .hgsubstate
26 42
27 43 delete .hgsub and update
28 44
29 45 $ rm .hgsub
30 46 $ hg up 0
31 47 warning: subrepo spec file .hgsub not found
32 48 warning: subrepo spec file .hgsub not found
33 49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 50 $ hg st
35 51 warning: subrepo spec file .hgsub not found
36 52 ! .hgsub
37 53 $ ls subrepo
38 54 a
39 55
40 56 delete .hgsubstate and update
41 57
42 58 $ hg up -C
43 59 warning: subrepo spec file .hgsub not found
44 60 warning: subrepo spec file .hgsub not found
45 61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 62 $ rm .hgsubstate
47 63 $ hg up 0
48 64 remote changed .hgsubstate which local deleted
49 65 use (c)hanged version or leave (d)eleted? c
50 66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 67 $ hg st
52 68 $ ls subrepo
53 69 a
General Comments 0
You need to be logged in to leave comments. Login now