##// END OF EJS Templates
fix wording and not-completely-trivial spelling errors and bad docstrings
Mads Kiilerich -
r17425:e95ec38f default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,467 +1,467
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import os
12 12 import errno
13 13 import platform
14 14 import shutil
15 15 import stat
16 16
17 17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 18 from mercurial.i18n import _
19 19
20 20 shortname = '.hglf'
21 21 longname = 'largefiles'
22 22
23 23
24 24 # -- Portability wrappers ----------------------------------------------
25 25
26 26 def dirstatewalk(dirstate, matcher, unknown=False, ignored=False):
27 27 return dirstate.walk(matcher, [], unknown, ignored)
28 28
29 29 def repoadd(repo, list):
30 30 add = repo[None].add
31 31 return add(list)
32 32
33 33 def reporemove(repo, list, unlink=False):
34 34 def remove(list, unlink):
35 35 wlock = repo.wlock()
36 36 try:
37 37 if unlink:
38 38 for f in list:
39 39 try:
40 40 util.unlinkpath(repo.wjoin(f))
41 41 except OSError, inst:
42 42 if inst.errno != errno.ENOENT:
43 43 raise
44 44 repo[None].forget(list)
45 45 finally:
46 46 wlock.release()
47 47 return remove(list, unlink=unlink)
48 48
49 49 def repoforget(repo, list):
50 50 forget = repo[None].forget
51 51 return forget(list)
52 52
53 53 def findoutgoing(repo, remote, force):
54 54 from mercurial import discovery
55 55 common, _anyinc, _heads = discovery.findcommonincoming(repo,
56 56 remote.peer(), force=force)
57 57 return repo.changelog.findmissing(common)
58 58
59 59 # -- Private worker functions ------------------------------------------
60 60
61 61 def getminsize(ui, assumelfiles, opt, default=10):
62 62 lfsize = opt
63 63 if not lfsize and assumelfiles:
64 64 lfsize = ui.config(longname, 'minsize', default=default)
65 65 if lfsize:
66 66 try:
67 67 lfsize = float(lfsize)
68 68 except ValueError:
69 69 raise util.Abort(_('largefiles: size must be number (not %s)\n')
70 70 % lfsize)
71 71 if lfsize is None:
72 72 raise util.Abort(_('minimum size for largefiles must be specified'))
73 73 return lfsize
74 74
75 75 def link(src, dest):
76 76 try:
77 77 util.oslink(src, dest)
78 78 except OSError:
79 79 # if hardlinks fail, fallback on atomic copy
80 80 dst = util.atomictempfile(dest)
81 81 for chunk in util.filechunkiter(open(src, 'rb')):
82 82 dst.write(chunk)
83 83 dst.close()
84 84 os.chmod(dest, os.stat(src).st_mode)
85 85
86 86 def usercachepath(ui, hash):
87 87 path = ui.configpath(longname, 'usercache', None)
88 88 if path:
89 89 path = os.path.join(path, hash)
90 90 else:
91 91 if os.name == 'nt':
92 92 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
93 93 if appdata:
94 94 path = os.path.join(appdata, longname, hash)
95 95 elif platform.system() == 'Darwin':
96 96 home = os.getenv('HOME')
97 97 if home:
98 98 path = os.path.join(home, 'Library', 'Caches',
99 99 longname, hash)
100 100 elif os.name == 'posix':
101 101 path = os.getenv('XDG_CACHE_HOME')
102 102 if path:
103 103 path = os.path.join(path, longname, hash)
104 104 else:
105 105 home = os.getenv('HOME')
106 106 if home:
107 107 path = os.path.join(home, '.cache', longname, hash)
108 108 else:
109 109 raise util.Abort(_('unknown operating system: %s\n') % os.name)
110 110 return path
111 111
112 112 def inusercache(ui, hash):
113 113 path = usercachepath(ui, hash)
114 114 return path and os.path.exists(path)
115 115
116 116 def findfile(repo, hash):
117 117 if instore(repo, hash):
118 118 repo.ui.note(_('found %s in store\n') % hash)
119 119 return storepath(repo, hash)
120 120 elif inusercache(repo.ui, hash):
121 121 repo.ui.note(_('found %s in system cache\n') % hash)
122 122 path = storepath(repo, hash)
123 123 util.makedirs(os.path.dirname(path))
124 124 link(usercachepath(repo.ui, hash), path)
125 125 return path
126 126 return None
127 127
128 128 class largefilesdirstate(dirstate.dirstate):
129 129 def __getitem__(self, key):
130 130 return super(largefilesdirstate, self).__getitem__(unixpath(key))
131 131 def normal(self, f):
132 132 return super(largefilesdirstate, self).normal(unixpath(f))
133 133 def remove(self, f):
134 134 return super(largefilesdirstate, self).remove(unixpath(f))
135 135 def add(self, f):
136 136 return super(largefilesdirstate, self).add(unixpath(f))
137 137 def drop(self, f):
138 138 return super(largefilesdirstate, self).drop(unixpath(f))
139 139 def forget(self, f):
140 140 return super(largefilesdirstate, self).forget(unixpath(f))
141 141 def normallookup(self, f):
142 142 return super(largefilesdirstate, self).normallookup(unixpath(f))
143 143
144 144 def openlfdirstate(ui, repo):
145 145 '''
146 146 Return a dirstate object that tracks largefiles: i.e. its root is
147 147 the repo root, but it is saved in .hg/largefiles/dirstate.
148 148 '''
149 149 admin = repo.join(longname)
150 150 opener = scmutil.opener(admin)
151 151 lfdirstate = largefilesdirstate(opener, ui, repo.root,
152 152 repo.dirstate._validate)
153 153
154 154 # If the largefiles dirstate does not exist, populate and create
155 155 # it. This ensures that we create it on the first meaningful
156 156 # largefiles operation in a new clone.
157 157 if not os.path.exists(os.path.join(admin, 'dirstate')):
158 158 util.makedirs(admin)
159 159 matcher = getstandinmatcher(repo)
160 160 for standin in dirstatewalk(repo.dirstate, matcher):
161 161 lfile = splitstandin(standin)
162 162 hash = readstandin(repo, lfile)
163 163 lfdirstate.normallookup(lfile)
164 164 try:
165 165 if hash == hashfile(repo.wjoin(lfile)):
166 166 lfdirstate.normal(lfile)
167 167 except OSError, err:
168 168 if err.errno != errno.ENOENT:
169 169 raise
170 170 return lfdirstate
171 171
172 172 def lfdirstatestatus(lfdirstate, repo, rev):
173 173 match = match_.always(repo.root, repo.getcwd())
174 174 s = lfdirstate.status(match, [], False, False, False)
175 175 unsure, modified, added, removed, missing, unknown, ignored, clean = s
176 176 for lfile in unsure:
177 177 if repo[rev][standin(lfile)].data().strip() != \
178 178 hashfile(repo.wjoin(lfile)):
179 179 modified.append(lfile)
180 180 else:
181 181 clean.append(lfile)
182 182 lfdirstate.normal(lfile)
183 183 return (modified, added, removed, missing, unknown, ignored, clean)
184 184
185 185 def listlfiles(repo, rev=None, matcher=None):
186 186 '''return a list of largefiles in the working copy or the
187 187 specified changeset'''
188 188
189 189 if matcher is None:
190 190 matcher = getstandinmatcher(repo)
191 191
192 192 # ignore unknown files in working directory
193 193 return [splitstandin(f)
194 194 for f in repo[rev].walk(matcher)
195 195 if rev is not None or repo.dirstate[f] != '?']
196 196
197 197 def instore(repo, hash):
198 198 return os.path.exists(storepath(repo, hash))
199 199
200 200 def storepath(repo, hash):
201 201 return repo.join(os.path.join(longname, hash))
202 202
203 203 def copyfromcache(repo, hash, filename):
204 204 '''Copy the specified largefile from the repo or system cache to
205 205 filename in the repository. Return true on success or false if the
206 206 file was not found in either cache (which should not happened:
207 207 this is meant to be called only after ensuring that the needed
208 208 largefile exists in the cache).'''
209 209 path = findfile(repo, hash)
210 210 if path is None:
211 211 return False
212 212 util.makedirs(os.path.dirname(repo.wjoin(filename)))
213 213 # The write may fail before the file is fully written, but we
214 214 # don't use atomic writes in the working copy.
215 215 shutil.copy(path, repo.wjoin(filename))
216 216 return True
217 217
218 218 def copytostore(repo, rev, file, uploaded=False):
219 219 hash = readstandin(repo, file)
220 220 if instore(repo, hash):
221 221 return
222 222 copytostoreabsolute(repo, repo.wjoin(file), hash)
223 223
224 224 def copyalltostore(repo, node):
225 225 '''Copy all largefiles in a given revision to the store'''
226 226
227 227 ctx = repo[node]
228 228 for filename in ctx.files():
229 229 if isstandin(filename) and filename in ctx.manifest():
230 230 realfile = splitstandin(filename)
231 231 copytostore(repo, ctx.node(), realfile)
232 232
233 233
234 234 def copytostoreabsolute(repo, file, hash):
235 235 util.makedirs(os.path.dirname(storepath(repo, hash)))
236 236 if inusercache(repo.ui, hash):
237 237 link(usercachepath(repo.ui, hash), storepath(repo, hash))
238 238 else:
239 239 dst = util.atomictempfile(storepath(repo, hash),
240 240 createmode=repo.store.createmode)
241 241 for chunk in util.filechunkiter(open(file, 'rb')):
242 242 dst.write(chunk)
243 243 dst.close()
244 244 linktousercache(repo, hash)
245 245
246 246 def linktousercache(repo, hash):
247 247 path = usercachepath(repo.ui, hash)
248 248 if path:
249 249 util.makedirs(os.path.dirname(path))
250 250 link(storepath(repo, hash), path)
251 251
252 252 def getstandinmatcher(repo, pats=[], opts={}):
253 253 '''Return a match object that applies pats to the standin directory'''
254 254 standindir = repo.pathto(shortname)
255 255 if pats:
256 256 # patterns supplied: search standin directory relative to current dir
257 257 cwd = repo.getcwd()
258 258 if os.path.isabs(cwd):
259 259 # cwd is an absolute path for hg -R <reponame>
260 260 # work relative to the repository root in this case
261 261 cwd = ''
262 262 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
263 263 elif os.path.isdir(standindir):
264 264 # no patterns: relative to repo root
265 265 pats = [standindir]
266 266 else:
267 267 # no patterns and no standin dir: return matcher that matches nothing
268 268 match = match_.match(repo.root, None, [], exact=True)
269 269 match.matchfn = lambda f: False
270 270 return match
271 271 return getmatcher(repo, pats, opts, showbad=False)
272 272
273 273 def getmatcher(repo, pats=[], opts={}, showbad=True):
274 274 '''Wrapper around scmutil.match() that adds showbad: if false,
275 275 neuter the match object's bad() method so it does not print any
276 276 warnings about missing files or directories.'''
277 277 match = scmutil.match(repo[None], pats, opts)
278 278
279 279 if not showbad:
280 280 match.bad = lambda f, msg: None
281 281 return match
282 282
283 283 def composestandinmatcher(repo, rmatcher):
284 284 '''Return a matcher that accepts standins corresponding to the
285 285 files accepted by rmatcher. Pass the list of files in the matcher
286 286 as the paths specified by the user.'''
287 287 smatcher = getstandinmatcher(repo, rmatcher.files())
288 288 isstandin = smatcher.matchfn
289 289 def composedmatchfn(f):
290 290 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
291 291 smatcher.matchfn = composedmatchfn
292 292
293 293 return smatcher
294 294
295 295 def standin(filename):
296 296 '''Return the repo-relative path to the standin for the specified big
297 297 file.'''
298 298 # Notes:
299 # 1) Most callers want an absolute path, but _createstandin() needs
300 # it repo-relative so lfadd() can pass it to repoadd(). So leave
299 # 1) Some callers want an absolute path, but for instance addlargefiles
300 # needs it repo-relative so it can be passed to repoadd(). So leave
301 301 # it up to the caller to use repo.wjoin() to get an absolute path.
302 302 # 2) Join with '/' because that's what dirstate always uses, even on
303 303 # Windows. Change existing separator to '/' first in case we are
304 304 # passed filenames from an external source (like the command line).
305 305 return shortname + '/' + util.pconvert(filename)
306 306
307 307 def isstandin(filename):
308 308 '''Return true if filename is a big file standin. filename must be
309 309 in Mercurial's internal form (slash-separated).'''
310 310 return filename.startswith(shortname + '/')
311 311
312 312 def splitstandin(filename):
313 313 # Split on / because that's what dirstate always uses, even on Windows.
314 314 # Change local separator to / first just in case we are passed filenames
315 315 # from an external source (like the command line).
316 316 bits = util.pconvert(filename).split('/', 1)
317 317 if len(bits) == 2 and bits[0] == shortname:
318 318 return bits[1]
319 319 else:
320 320 return None
321 321
322 322 def updatestandin(repo, standin):
323 323 file = repo.wjoin(splitstandin(standin))
324 324 if os.path.exists(file):
325 325 hash = hashfile(file)
326 326 executable = getexecutable(file)
327 327 writestandin(repo, standin, hash, executable)
328 328
329 329 def readstandin(repo, filename, node=None):
330 330 '''read hex hash from standin for filename at given node, or working
331 331 directory if no node is given'''
332 332 return repo[node][standin(filename)].data().strip()
333 333
334 334 def writestandin(repo, standin, hash, executable):
335 335 '''write hash to <repo.root>/<standin>'''
336 336 writehash(hash, repo.wjoin(standin), executable)
337 337
338 338 def copyandhash(instream, outfile):
339 339 '''Read bytes from instream (iterable) and write them to outfile,
340 340 computing the SHA-1 hash of the data along the way. Close outfile
341 341 when done and return the binary hash.'''
342 342 hasher = util.sha1('')
343 343 for data in instream:
344 344 hasher.update(data)
345 345 outfile.write(data)
346 346
347 347 # Blecch: closing a file that somebody else opened is rude and
348 348 # wrong. But it's so darn convenient and practical! After all,
349 349 # outfile was opened just to copy and hash.
350 350 outfile.close()
351 351
352 352 return hasher.digest()
353 353
354 354 def hashrepofile(repo, file):
355 355 return hashfile(repo.wjoin(file))
356 356
357 357 def hashfile(file):
358 358 if not os.path.exists(file):
359 359 return ''
360 360 hasher = util.sha1('')
361 361 fd = open(file, 'rb')
362 362 for data in blockstream(fd):
363 363 hasher.update(data)
364 364 fd.close()
365 365 return hasher.hexdigest()
366 366
367 367 class limitreader(object):
368 368 def __init__(self, f, limit):
369 369 self.f = f
370 370 self.limit = limit
371 371
372 372 def read(self, length):
373 373 if self.limit == 0:
374 374 return ''
375 375 length = length > self.limit and self.limit or length
376 376 self.limit -= length
377 377 return self.f.read(length)
378 378
379 379 def close(self):
380 380 pass
381 381
382 382 def blockstream(infile, blocksize=128 * 1024):
383 383 """Generator that yields blocks of data from infile and closes infile."""
384 384 while True:
385 385 data = infile.read(blocksize)
386 386 if not data:
387 387 break
388 388 yield data
389 389 # same blecch as copyandhash() above
390 390 infile.close()
391 391
392 392 def writehash(hash, filename, executable):
393 393 util.makedirs(os.path.dirname(filename))
394 394 util.writefile(filename, hash + '\n')
395 395 os.chmod(filename, getmode(executable))
396 396
397 397 def getexecutable(filename):
398 398 mode = os.stat(filename).st_mode
399 399 return ((mode & stat.S_IXUSR) and
400 400 (mode & stat.S_IXGRP) and
401 401 (mode & stat.S_IXOTH))
402 402
403 403 def getmode(executable):
404 404 if executable:
405 405 return 0755
406 406 else:
407 407 return 0644
408 408
409 409 def urljoin(first, second, *arg):
410 410 def join(left, right):
411 411 if not left.endswith('/'):
412 412 left += '/'
413 413 if right.startswith('/'):
414 414 right = right[1:]
415 415 return left + right
416 416
417 417 url = join(first, second)
418 418 for a in arg:
419 419 url = join(url, a)
420 420 return url
421 421
422 422 def hexsha1(data):
423 423 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
424 424 object data"""
425 425 h = util.sha1()
426 426 for chunk in util.filechunkiter(data):
427 427 h.update(chunk)
428 428 return h.hexdigest()
429 429
430 430 def httpsendfile(ui, filename):
431 431 return httpconnection.httpsendfile(ui, filename, 'rb')
432 432
433 433 def unixpath(path):
434 434 '''Return a version of path normalized for use with the lfdirstate.'''
435 435 return util.pconvert(os.path.normpath(path))
436 436
437 437 def islfilesrepo(repo):
438 438 return ('largefiles' in repo.requirements and
439 439 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
440 440
441 441 class storeprotonotcapable(Exception):
442 442 def __init__(self, storetypes):
443 443 self.storetypes = storetypes
444 444
445 445 def getcurrentheads(repo):
446 446 branches = repo.branchmap()
447 447 heads = []
448 448 for branch in branches:
449 449 newheads = repo.branchheads(branch)
450 450 heads = heads + newheads
451 451 return heads
452 452
453 453 def getstandinsstate(repo):
454 454 standins = []
455 455 matcher = getstandinmatcher(repo)
456 456 for standin in dirstatewalk(repo.dirstate, matcher):
457 457 lfile = splitstandin(standin)
458 458 standins.append((lfile, readstandin(repo, lfile)))
459 459 return standins
460 460
461 461 def getlfilestoupdate(oldstandins, newstandins):
462 462 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
463 463 filelist = []
464 464 for f in changedstandins:
465 465 if f[0] not in filelist:
466 466 filelist.append(f[0])
467 467 return filelist
@@ -1,110 +1,110
1 1 # Copyright 2010-2011 Fog Creek Software
2 2 # Copyright 2010-2011 Unity Technologies
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 '''remote largefile store; the base class for servestore'''
7 '''remote largefile store; the base class for wirestore'''
8 8
9 9 import urllib2
10 10
11 11 from mercurial import util
12 12 from mercurial.i18n import _
13 13 from mercurial.wireproto import remotebatch
14 14
15 15 import lfutil
16 16 import basestore
17 17
18 18 class remotestore(basestore.basestore):
19 19 '''a largefile store accessed over a network'''
20 20 def __init__(self, ui, repo, url):
21 21 super(remotestore, self).__init__(ui, repo, url)
22 22
23 23 def put(self, source, hash):
24 24 if self.sendfile(source, hash):
25 25 raise util.Abort(
26 26 _('remotestore: could not put %s to remote store %s')
27 27 % (source, self.url))
28 28 self.ui.debug(
29 29 _('remotestore: put %s to remote store %s') % (source, self.url))
30 30
31 31 def exists(self, hashes):
32 32 return self._verify(hashes)
33 33
34 34 def sendfile(self, filename, hash):
35 35 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
36 36 fd = None
37 37 try:
38 38 try:
39 39 fd = lfutil.httpsendfile(self.ui, filename)
40 40 except IOError, e:
41 41 raise util.Abort(
42 42 _('remotestore: could not open file %s: %s')
43 43 % (filename, str(e)))
44 44 return self._put(hash, fd)
45 45 finally:
46 46 if fd:
47 47 fd.close()
48 48
49 49 def _getfile(self, tmpfile, filename, hash):
50 50 # quit if the largefile isn't there
51 51 stat = self._stat(hash)
52 52 if stat == 1:
53 53 raise util.Abort(_('remotestore: largefile %s is invalid') % hash)
54 54 elif stat == 2:
55 55 raise util.Abort(_('remotestore: largefile %s is missing') % hash)
56 56
57 57 try:
58 58 length, infile = self._get(hash)
59 59 except urllib2.HTTPError, e:
60 60 # 401s get converted to util.Aborts; everything else is fine being
61 61 # turned into a StoreError
62 62 raise basestore.StoreError(filename, hash, self.url, str(e))
63 63 except urllib2.URLError, e:
64 64 # This usually indicates a connection problem, so don't
65 65 # keep trying with the other files... they will probably
66 66 # all fail too.
67 67 raise util.Abort('%s: %s' % (self.url, e.reason))
68 68 except IOError, e:
69 69 raise basestore.StoreError(filename, hash, self.url, str(e))
70 70
71 71 # Mercurial does not close its SSH connections after writing a stream
72 72 if length is not None:
73 73 infile = lfutil.limitreader(infile, length)
74 74 return lfutil.copyandhash(lfutil.blockstream(infile), tmpfile)
75 75
76 76 def _verify(self, hashes):
77 77 return self._stat(hashes)
78 78
79 79 def _verifyfile(self, cctx, cset, contents, standin, verified):
80 80 filename = lfutil.splitstandin(standin)
81 81 if not filename:
82 82 return False
83 83 fctx = cctx[standin]
84 84 key = (filename, fctx.filenode())
85 85 if key in verified:
86 86 return False
87 87
88 88 verified.add(key)
89 89
90 90 stat = self._stat(hash)
91 91 if not stat:
92 92 return False
93 93 elif stat == 1:
94 94 self.ui.warn(
95 95 _('changeset %s: %s: contents differ\n')
96 96 % (cset, filename))
97 97 return True # failed
98 98 elif stat == 2:
99 99 self.ui.warn(
100 100 _('changeset %s: %s missing\n')
101 101 % (cset, filename))
102 102 return True # failed
103 103 else:
104 104 raise RuntimeError('verify failed: unexpected response from '
105 105 'statlfile (%r)' % stat)
106 106
107 107 def batch(self):
108 108 '''Support for remote batching.'''
109 109 return remotebatch(self)
110 110
@@ -1,3597 +1,3597
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.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 '''manage a stack of patches
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use :hg:`help command` for more details)::
18 18
19 19 create new patch qnew
20 20 import existing patch qimport
21 21
22 22 print patch series qseries
23 23 print applied patches qapplied
24 24
25 25 add known patch to applied stack qpush
26 26 remove patch from applied stack qpop
27 27 refresh contents of top applied patch qrefresh
28 28
29 29 By default, mq will automatically use git patches when required to
30 30 avoid losing file mode changes, copy records, binary files or empty
31 31 files creations or deletions. This behaviour can be configured with::
32 32
33 33 [mq]
34 34 git = auto/keep/yes/no
35 35
36 36 If set to 'keep', mq will obey the [diff] section configuration while
37 37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 38 'no', mq will override the [diff] section and always generate git or
39 39 regular patches, possibly losing data in the second case.
40 40
41 41 It may be desirable for mq changesets to be kept in the secret phase (see
42 42 :hg:`help phases`), which can be enabled with the following setting::
43 43
44 44 [mq]
45 45 secret = True
46 46
47 47 You will by default be managing a patch queue named "patches". You can
48 48 create other, independent patch queues with the :hg:`qqueue` command.
49 49
50 50 If the working directory contains uncommitted files, qpush, qpop and
51 51 qgoto abort immediately. If -f/--force is used, the changes are
52 52 discarded. Setting::
53 53
54 54 [mq]
55 55 keepchanges = True
56 56
57 57 make them behave as if --keep-changes were passed, and non-conflicting
58 58 local changes will be tolerated and preserved. If incompatible options
59 59 such as -f/--force or --exact are passed, this setting is ignored.
60 60 '''
61 61
62 62 from mercurial.i18n import _
63 63 from mercurial.node import bin, hex, short, nullid, nullrev
64 64 from mercurial.lock import release
65 65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
66 66 from mercurial import repair, extensions, url, error, phases
67 67 from mercurial import patch as patchmod
68 68 import os, re, errno, shutil
69 69
70 70 commands.norepo += " qclone"
71 71
72 72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
73 73
74 74 cmdtable = {}
75 75 command = cmdutil.command(cmdtable)
76 76 testedwith = 'internal'
77 77
78 78 # Patch names looks like unix-file names.
79 79 # They must be joinable with queue directory and result in the patch path.
80 80 normname = util.normpath
81 81
82 82 class statusentry(object):
83 83 def __init__(self, node, name):
84 84 self.node, self.name = node, name
85 85 def __repr__(self):
86 86 return hex(self.node) + ':' + self.name
87 87
88 88 class patchheader(object):
89 89 def __init__(self, pf, plainmode=False):
90 90 def eatdiff(lines):
91 91 while lines:
92 92 l = lines[-1]
93 93 if (l.startswith("diff -") or
94 94 l.startswith("Index:") or
95 95 l.startswith("===========")):
96 96 del lines[-1]
97 97 else:
98 98 break
99 99 def eatempty(lines):
100 100 while lines:
101 101 if not lines[-1].strip():
102 102 del lines[-1]
103 103 else:
104 104 break
105 105
106 106 message = []
107 107 comments = []
108 108 user = None
109 109 date = None
110 110 parent = None
111 111 format = None
112 112 subject = None
113 113 branch = None
114 114 nodeid = None
115 115 diffstart = 0
116 116
117 117 for line in file(pf):
118 118 line = line.rstrip()
119 119 if (line.startswith('diff --git')
120 120 or (diffstart and line.startswith('+++ '))):
121 121 diffstart = 2
122 122 break
123 123 diffstart = 0 # reset
124 124 if line.startswith("--- "):
125 125 diffstart = 1
126 126 continue
127 127 elif format == "hgpatch":
128 128 # parse values when importing the result of an hg export
129 129 if line.startswith("# User "):
130 130 user = line[7:]
131 131 elif line.startswith("# Date "):
132 132 date = line[7:]
133 133 elif line.startswith("# Parent "):
134 134 parent = line[9:].lstrip()
135 135 elif line.startswith("# Branch "):
136 136 branch = line[9:]
137 137 elif line.startswith("# Node ID "):
138 138 nodeid = line[10:]
139 139 elif not line.startswith("# ") and line:
140 140 message.append(line)
141 141 format = None
142 142 elif line == '# HG changeset patch':
143 143 message = []
144 144 format = "hgpatch"
145 145 elif (format != "tagdone" and (line.startswith("Subject: ") or
146 146 line.startswith("subject: "))):
147 147 subject = line[9:]
148 148 format = "tag"
149 149 elif (format != "tagdone" and (line.startswith("From: ") or
150 150 line.startswith("from: "))):
151 151 user = line[6:]
152 152 format = "tag"
153 153 elif (format != "tagdone" and (line.startswith("Date: ") or
154 154 line.startswith("date: "))):
155 155 date = line[6:]
156 156 format = "tag"
157 157 elif format == "tag" and line == "":
158 158 # when looking for tags (subject: from: etc) they
159 159 # end once you find a blank line in the source
160 160 format = "tagdone"
161 161 elif message or line:
162 162 message.append(line)
163 163 comments.append(line)
164 164
165 165 eatdiff(message)
166 166 eatdiff(comments)
167 167 # Remember the exact starting line of the patch diffs before consuming
168 168 # empty lines, for external use by TortoiseHg and others
169 169 self.diffstartline = len(comments)
170 170 eatempty(message)
171 171 eatempty(comments)
172 172
173 173 # make sure message isn't empty
174 174 if format and format.startswith("tag") and subject:
175 175 message.insert(0, "")
176 176 message.insert(0, subject)
177 177
178 178 self.message = message
179 179 self.comments = comments
180 180 self.user = user
181 181 self.date = date
182 182 self.parent = parent
183 183 # nodeid and branch are for external use by TortoiseHg and others
184 184 self.nodeid = nodeid
185 185 self.branch = branch
186 186 self.haspatch = diffstart > 1
187 187 self.plainmode = plainmode
188 188
189 189 def setuser(self, user):
190 190 if not self.updateheader(['From: ', '# User '], user):
191 191 try:
192 192 patchheaderat = self.comments.index('# HG changeset patch')
193 193 self.comments.insert(patchheaderat + 1, '# User ' + user)
194 194 except ValueError:
195 195 if self.plainmode or self._hasheader(['Date: ']):
196 196 self.comments = ['From: ' + user] + self.comments
197 197 else:
198 198 tmp = ['# HG changeset patch', '# User ' + user, '']
199 199 self.comments = tmp + self.comments
200 200 self.user = user
201 201
202 202 def setdate(self, date):
203 203 if not self.updateheader(['Date: ', '# Date '], date):
204 204 try:
205 205 patchheaderat = self.comments.index('# HG changeset patch')
206 206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
207 207 except ValueError:
208 208 if self.plainmode or self._hasheader(['From: ']):
209 209 self.comments = ['Date: ' + date] + self.comments
210 210 else:
211 211 tmp = ['# HG changeset patch', '# Date ' + date, '']
212 212 self.comments = tmp + self.comments
213 213 self.date = date
214 214
215 215 def setparent(self, parent):
216 216 if not self.updateheader(['# Parent '], parent):
217 217 try:
218 218 patchheaderat = self.comments.index('# HG changeset patch')
219 219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
220 220 except ValueError:
221 221 pass
222 222 self.parent = parent
223 223
224 224 def setmessage(self, message):
225 225 if self.comments:
226 226 self._delmsg()
227 227 self.message = [message]
228 228 self.comments += self.message
229 229
230 230 def updateheader(self, prefixes, new):
231 231 '''Update all references to a field in the patch header.
232 232 Return whether the field is present.'''
233 233 res = False
234 234 for prefix in prefixes:
235 235 for i in xrange(len(self.comments)):
236 236 if self.comments[i].startswith(prefix):
237 237 self.comments[i] = prefix + new
238 238 res = True
239 239 break
240 240 return res
241 241
242 242 def _hasheader(self, prefixes):
243 243 '''Check if a header starts with any of the given prefixes.'''
244 244 for prefix in prefixes:
245 245 for comment in self.comments:
246 246 if comment.startswith(prefix):
247 247 return True
248 248 return False
249 249
250 250 def __str__(self):
251 251 if not self.comments:
252 252 return ''
253 253 return '\n'.join(self.comments) + '\n\n'
254 254
255 255 def _delmsg(self):
256 256 '''Remove existing message, keeping the rest of the comments fields.
257 257 If comments contains 'subject: ', message will prepend
258 258 the field and a blank line.'''
259 259 if self.message:
260 260 subj = 'subject: ' + self.message[0].lower()
261 261 for i in xrange(len(self.comments)):
262 262 if subj == self.comments[i].lower():
263 263 del self.comments[i]
264 264 self.message = self.message[2:]
265 265 break
266 266 ci = 0
267 267 for mi in self.message:
268 268 while mi != self.comments[ci]:
269 269 ci += 1
270 270 del self.comments[ci]
271 271
272 272 def newcommit(repo, phase, *args, **kwargs):
273 273 """helper dedicated to ensure a commit respect mq.secret setting
274 274
275 275 It should be used instead of repo.commit inside the mq source for operation
276 276 creating new changeset.
277 277 """
278 278 if phase is None:
279 279 if repo.ui.configbool('mq', 'secret', False):
280 280 phase = phases.secret
281 281 if phase is not None:
282 282 backup = repo.ui.backupconfig('phases', 'new-commit')
283 283 # Marking the repository as committing an mq patch can be used
284 284 # to optimize operations like _branchtags().
285 285 repo._committingpatch = True
286 286 try:
287 287 if phase is not None:
288 288 repo.ui.setconfig('phases', 'new-commit', phase)
289 289 return repo.commit(*args, **kwargs)
290 290 finally:
291 291 repo._committingpatch = False
292 292 if phase is not None:
293 293 repo.ui.restoreconfig(backup)
294 294
295 295 class AbortNoCleanup(error.Abort):
296 296 pass
297 297
298 298 class queue(object):
299 299 def __init__(self, ui, path, patchdir=None):
300 300 self.basepath = path
301 301 try:
302 302 fh = open(os.path.join(path, 'patches.queue'))
303 303 cur = fh.read().rstrip()
304 304 fh.close()
305 305 if not cur:
306 306 curpath = os.path.join(path, 'patches')
307 307 else:
308 308 curpath = os.path.join(path, 'patches-' + cur)
309 309 except IOError:
310 310 curpath = os.path.join(path, 'patches')
311 311 self.path = patchdir or curpath
312 312 self.opener = scmutil.opener(self.path)
313 313 self.ui = ui
314 314 self.applieddirty = False
315 315 self.seriesdirty = False
316 316 self.added = []
317 317 self.seriespath = "series"
318 318 self.statuspath = "status"
319 319 self.guardspath = "guards"
320 320 self.activeguards = None
321 321 self.guardsdirty = False
322 322 # Handle mq.git as a bool with extended values
323 323 try:
324 324 gitmode = ui.configbool('mq', 'git', None)
325 325 if gitmode is None:
326 326 raise error.ConfigError
327 327 self.gitmode = gitmode and 'yes' or 'no'
328 328 except error.ConfigError:
329 329 self.gitmode = ui.config('mq', 'git', 'auto').lower()
330 330 self.plainmode = ui.configbool('mq', 'plain', False)
331 331
332 332 @util.propertycache
333 333 def applied(self):
334 334 def parselines(lines):
335 335 for l in lines:
336 336 entry = l.split(':', 1)
337 337 if len(entry) > 1:
338 338 n, name = entry
339 339 yield statusentry(bin(n), name)
340 340 elif l.strip():
341 341 self.ui.warn(_('malformated mq status line: %s\n') % entry)
342 342 # else we ignore empty lines
343 343 try:
344 344 lines = self.opener.read(self.statuspath).splitlines()
345 345 return list(parselines(lines))
346 346 except IOError, e:
347 347 if e.errno == errno.ENOENT:
348 348 return []
349 349 raise
350 350
351 351 @util.propertycache
352 352 def fullseries(self):
353 353 try:
354 354 return self.opener.read(self.seriespath).splitlines()
355 355 except IOError, e:
356 356 if e.errno == errno.ENOENT:
357 357 return []
358 358 raise
359 359
360 360 @util.propertycache
361 361 def series(self):
362 362 self.parseseries()
363 363 return self.series
364 364
365 365 @util.propertycache
366 366 def seriesguards(self):
367 367 self.parseseries()
368 368 return self.seriesguards
369 369
370 370 def invalidate(self):
371 371 for a in 'applied fullseries series seriesguards'.split():
372 372 if a in self.__dict__:
373 373 delattr(self, a)
374 374 self.applieddirty = False
375 375 self.seriesdirty = False
376 376 self.guardsdirty = False
377 377 self.activeguards = None
378 378
379 379 def diffopts(self, opts={}, patchfn=None):
380 380 diffopts = patchmod.diffopts(self.ui, opts)
381 381 if self.gitmode == 'auto':
382 382 diffopts.upgrade = True
383 383 elif self.gitmode == 'keep':
384 384 pass
385 385 elif self.gitmode in ('yes', 'no'):
386 386 diffopts.git = self.gitmode == 'yes'
387 387 else:
388 388 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
389 389 ' got %s') % self.gitmode)
390 390 if patchfn:
391 391 diffopts = self.patchopts(diffopts, patchfn)
392 392 return diffopts
393 393
394 394 def patchopts(self, diffopts, *patches):
395 395 """Return a copy of input diff options with git set to true if
396 396 referenced patch is a git patch and should be preserved as such.
397 397 """
398 398 diffopts = diffopts.copy()
399 399 if not diffopts.git and self.gitmode == 'keep':
400 400 for patchfn in patches:
401 401 patchf = self.opener(patchfn, 'r')
402 402 # if the patch was a git patch, refresh it as a git patch
403 403 for line in patchf:
404 404 if line.startswith('diff --git'):
405 405 diffopts.git = True
406 406 break
407 407 patchf.close()
408 408 return diffopts
409 409
410 410 def join(self, *p):
411 411 return os.path.join(self.path, *p)
412 412
413 413 def findseries(self, patch):
414 414 def matchpatch(l):
415 415 l = l.split('#', 1)[0]
416 416 return l.strip() == patch
417 417 for index, l in enumerate(self.fullseries):
418 418 if matchpatch(l):
419 419 return index
420 420 return None
421 421
422 422 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
423 423
424 424 def parseseries(self):
425 425 self.series = []
426 426 self.seriesguards = []
427 427 for l in self.fullseries:
428 428 h = l.find('#')
429 429 if h == -1:
430 430 patch = l
431 431 comment = ''
432 432 elif h == 0:
433 433 continue
434 434 else:
435 435 patch = l[:h]
436 436 comment = l[h:]
437 437 patch = patch.strip()
438 438 if patch:
439 439 if patch in self.series:
440 440 raise util.Abort(_('%s appears more than once in %s') %
441 441 (patch, self.join(self.seriespath)))
442 442 self.series.append(patch)
443 443 self.seriesguards.append(self.guard_re.findall(comment))
444 444
445 445 def checkguard(self, guard):
446 446 if not guard:
447 447 return _('guard cannot be an empty string')
448 448 bad_chars = '# \t\r\n\f'
449 449 first = guard[0]
450 450 if first in '-+':
451 451 return (_('guard %r starts with invalid character: %r') %
452 452 (guard, first))
453 453 for c in bad_chars:
454 454 if c in guard:
455 455 return _('invalid character in guard %r: %r') % (guard, c)
456 456
457 457 def setactive(self, guards):
458 458 for guard in guards:
459 459 bad = self.checkguard(guard)
460 460 if bad:
461 461 raise util.Abort(bad)
462 462 guards = sorted(set(guards))
463 463 self.ui.debug('active guards: %s\n' % ' '.join(guards))
464 464 self.activeguards = guards
465 465 self.guardsdirty = True
466 466
467 467 def active(self):
468 468 if self.activeguards is None:
469 469 self.activeguards = []
470 470 try:
471 471 guards = self.opener.read(self.guardspath).split()
472 472 except IOError, err:
473 473 if err.errno != errno.ENOENT:
474 474 raise
475 475 guards = []
476 476 for i, guard in enumerate(guards):
477 477 bad = self.checkguard(guard)
478 478 if bad:
479 479 self.ui.warn('%s:%d: %s\n' %
480 480 (self.join(self.guardspath), i + 1, bad))
481 481 else:
482 482 self.activeguards.append(guard)
483 483 return self.activeguards
484 484
485 485 def setguards(self, idx, guards):
486 486 for g in guards:
487 487 if len(g) < 2:
488 488 raise util.Abort(_('guard %r too short') % g)
489 489 if g[0] not in '-+':
490 490 raise util.Abort(_('guard %r starts with invalid char') % g)
491 491 bad = self.checkguard(g[1:])
492 492 if bad:
493 493 raise util.Abort(bad)
494 494 drop = self.guard_re.sub('', self.fullseries[idx])
495 495 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
496 496 self.parseseries()
497 497 self.seriesdirty = True
498 498
499 499 def pushable(self, idx):
500 500 if isinstance(idx, str):
501 501 idx = self.series.index(idx)
502 502 patchguards = self.seriesguards[idx]
503 503 if not patchguards:
504 504 return True, None
505 505 guards = self.active()
506 506 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
507 507 if exactneg:
508 508 return False, repr(exactneg[0])
509 509 pos = [g for g in patchguards if g[0] == '+']
510 510 exactpos = [g for g in pos if g[1:] in guards]
511 511 if pos:
512 512 if exactpos:
513 513 return True, repr(exactpos[0])
514 514 return False, ' '.join(map(repr, pos))
515 515 return True, ''
516 516
517 517 def explainpushable(self, idx, all_patches=False):
518 518 write = all_patches and self.ui.write or self.ui.warn
519 519 if all_patches or self.ui.verbose:
520 520 if isinstance(idx, str):
521 521 idx = self.series.index(idx)
522 522 pushable, why = self.pushable(idx)
523 523 if all_patches and pushable:
524 524 if why is None:
525 525 write(_('allowing %s - no guards in effect\n') %
526 526 self.series[idx])
527 527 else:
528 528 if not why:
529 529 write(_('allowing %s - no matching negative guards\n') %
530 530 self.series[idx])
531 531 else:
532 532 write(_('allowing %s - guarded by %s\n') %
533 533 (self.series[idx], why))
534 534 if not pushable:
535 535 if why:
536 536 write(_('skipping %s - guarded by %s\n') %
537 537 (self.series[idx], why))
538 538 else:
539 539 write(_('skipping %s - no matching guards\n') %
540 540 self.series[idx])
541 541
542 542 def savedirty(self):
543 543 def writelist(items, path):
544 544 fp = self.opener(path, 'w')
545 545 for i in items:
546 546 fp.write("%s\n" % i)
547 547 fp.close()
548 548 if self.applieddirty:
549 549 writelist(map(str, self.applied), self.statuspath)
550 550 self.applieddirty = False
551 551 if self.seriesdirty:
552 552 writelist(self.fullseries, self.seriespath)
553 553 self.seriesdirty = False
554 554 if self.guardsdirty:
555 555 writelist(self.activeguards, self.guardspath)
556 556 self.guardsdirty = False
557 557 if self.added:
558 558 qrepo = self.qrepo()
559 559 if qrepo:
560 560 qrepo[None].add(f for f in self.added if f not in qrepo[None])
561 561 self.added = []
562 562
563 563 def removeundo(self, repo):
564 564 undo = repo.sjoin('undo')
565 565 if not os.path.exists(undo):
566 566 return
567 567 try:
568 568 os.unlink(undo)
569 569 except OSError, inst:
570 570 self.ui.warn(_('error removing undo: %s\n') % str(inst))
571 571
572 572 def backup(self, repo, files, copy=False):
573 573 # backup local changes in --force case
574 574 for f in sorted(files):
575 575 absf = repo.wjoin(f)
576 576 if os.path.lexists(absf):
577 577 self.ui.note(_('saving current version of %s as %s\n') %
578 578 (f, f + '.orig'))
579 579 if copy:
580 580 util.copyfile(absf, absf + '.orig')
581 581 else:
582 582 util.rename(absf, absf + '.orig')
583 583
584 584 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
585 585 fp=None, changes=None, opts={}):
586 586 stat = opts.get('stat')
587 587 m = scmutil.match(repo[node1], files, opts)
588 588 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
589 589 changes, stat, fp)
590 590
591 591 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
592 592 # first try just applying the patch
593 593 (err, n) = self.apply(repo, [patch], update_status=False,
594 594 strict=True, merge=rev)
595 595
596 596 if err == 0:
597 597 return (err, n)
598 598
599 599 if n is None:
600 600 raise util.Abort(_("apply failed for patch %s") % patch)
601 601
602 602 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
603 603
604 604 # apply failed, strip away that rev and merge.
605 605 hg.clean(repo, head)
606 606 self.strip(repo, [n], update=False, backup='strip')
607 607
608 608 ctx = repo[rev]
609 609 ret = hg.merge(repo, rev)
610 610 if ret:
611 611 raise util.Abort(_("update returned %d") % ret)
612 612 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
613 613 if n is None:
614 614 raise util.Abort(_("repo commit failed"))
615 615 try:
616 616 ph = patchheader(mergeq.join(patch), self.plainmode)
617 617 except Exception:
618 618 raise util.Abort(_("unable to read %s") % patch)
619 619
620 620 diffopts = self.patchopts(diffopts, patch)
621 621 patchf = self.opener(patch, "w")
622 622 comments = str(ph)
623 623 if comments:
624 624 patchf.write(comments)
625 625 self.printdiff(repo, diffopts, head, n, fp=patchf)
626 626 patchf.close()
627 627 self.removeundo(repo)
628 628 return (0, n)
629 629
630 630 def qparents(self, repo, rev=None):
631 631 if rev is None:
632 632 (p1, p2) = repo.dirstate.parents()
633 633 if p2 == nullid:
634 634 return p1
635 635 if not self.applied:
636 636 return None
637 637 return self.applied[-1].node
638 638 p1, p2 = repo.changelog.parents(rev)
639 639 if p2 != nullid and p2 in [x.node for x in self.applied]:
640 640 return p2
641 641 return p1
642 642
643 643 def mergepatch(self, repo, mergeq, series, diffopts):
644 644 if not self.applied:
645 645 # each of the patches merged in will have two parents. This
646 646 # can confuse the qrefresh, qdiff, and strip code because it
647 647 # needs to know which parent is actually in the patch queue.
648 648 # so, we insert a merge marker with only one parent. This way
649 649 # the first patch in the queue is never a merge patch
650 650 #
651 651 pname = ".hg.patches.merge.marker"
652 652 n = newcommit(repo, None, '[mq]: merge marker', force=True)
653 653 self.removeundo(repo)
654 654 self.applied.append(statusentry(n, pname))
655 655 self.applieddirty = True
656 656
657 657 head = self.qparents(repo)
658 658
659 659 for patch in series:
660 660 patch = mergeq.lookup(patch, strict=True)
661 661 if not patch:
662 662 self.ui.warn(_("patch %s does not exist\n") % patch)
663 663 return (1, None)
664 664 pushable, reason = self.pushable(patch)
665 665 if not pushable:
666 666 self.explainpushable(patch, all_patches=True)
667 667 continue
668 668 info = mergeq.isapplied(patch)
669 669 if not info:
670 670 self.ui.warn(_("patch %s is not applied\n") % patch)
671 671 return (1, None)
672 672 rev = info[1]
673 673 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
674 674 if head:
675 675 self.applied.append(statusentry(head, patch))
676 676 self.applieddirty = True
677 677 if err:
678 678 return (err, head)
679 679 self.savedirty()
680 680 return (0, head)
681 681
682 682 def patch(self, repo, patchfile):
683 683 '''Apply patchfile to the working directory.
684 684 patchfile: name of patch file'''
685 685 files = set()
686 686 try:
687 687 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
688 688 files=files, eolmode=None)
689 689 return (True, list(files), fuzz)
690 690 except Exception, inst:
691 691 self.ui.note(str(inst) + '\n')
692 692 if not self.ui.verbose:
693 693 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
694 694 self.ui.traceback()
695 695 return (False, list(files), False)
696 696
697 697 def apply(self, repo, series, list=False, update_status=True,
698 698 strict=False, patchdir=None, merge=None, all_files=None,
699 699 tobackup=None, keepchanges=False):
700 700 wlock = lock = tr = None
701 701 try:
702 702 wlock = repo.wlock()
703 703 lock = repo.lock()
704 704 tr = repo.transaction("qpush")
705 705 try:
706 706 ret = self._apply(repo, series, list, update_status,
707 707 strict, patchdir, merge, all_files=all_files,
708 708 tobackup=tobackup, keepchanges=keepchanges)
709 709 tr.close()
710 710 self.savedirty()
711 711 return ret
712 712 except AbortNoCleanup:
713 713 tr.close()
714 714 self.savedirty()
715 715 return 2, repo.dirstate.p1()
716 716 except: # re-raises
717 717 try:
718 718 tr.abort()
719 719 finally:
720 720 repo.invalidate()
721 721 repo.dirstate.invalidate()
722 722 self.invalidate()
723 723 raise
724 724 finally:
725 725 release(tr, lock, wlock)
726 726 self.removeundo(repo)
727 727
728 728 def _apply(self, repo, series, list=False, update_status=True,
729 729 strict=False, patchdir=None, merge=None, all_files=None,
730 730 tobackup=None, keepchanges=False):
731 731 """returns (error, hash)
732 732
733 733 error = 1 for unable to read, 2 for patch failed, 3 for patch
734 734 fuzz. tobackup is None or a set of files to backup before they
735 735 are modified by a patch.
736 736 """
737 737 # TODO unify with commands.py
738 738 if not patchdir:
739 739 patchdir = self.path
740 740 err = 0
741 741 n = None
742 742 for patchname in series:
743 743 pushable, reason = self.pushable(patchname)
744 744 if not pushable:
745 745 self.explainpushable(patchname, all_patches=True)
746 746 continue
747 747 self.ui.status(_("applying %s\n") % patchname)
748 748 pf = os.path.join(patchdir, patchname)
749 749
750 750 try:
751 751 ph = patchheader(self.join(patchname), self.plainmode)
752 752 except IOError:
753 753 self.ui.warn(_("unable to read %s\n") % patchname)
754 754 err = 1
755 755 break
756 756
757 757 message = ph.message
758 758 if not message:
759 759 # The commit message should not be translated
760 760 message = "imported patch %s\n" % patchname
761 761 else:
762 762 if list:
763 763 # The commit message should not be translated
764 764 message.append("\nimported patch %s" % patchname)
765 765 message = '\n'.join(message)
766 766
767 767 if ph.haspatch:
768 768 if tobackup:
769 769 touched = patchmod.changedfiles(self.ui, repo, pf)
770 770 touched = set(touched) & tobackup
771 771 if touched and keepchanges:
772 772 raise AbortNoCleanup(
773 773 _("local changes found, refresh first"))
774 774 self.backup(repo, touched, copy=True)
775 775 tobackup = tobackup - touched
776 776 (patcherr, files, fuzz) = self.patch(repo, pf)
777 777 if all_files is not None:
778 778 all_files.update(files)
779 779 patcherr = not patcherr
780 780 else:
781 781 self.ui.warn(_("patch %s is empty\n") % patchname)
782 782 patcherr, files, fuzz = 0, [], 0
783 783
784 784 if merge and files:
785 785 # Mark as removed/merged and update dirstate parent info
786 786 removed = []
787 787 merged = []
788 788 for f in files:
789 789 if os.path.lexists(repo.wjoin(f)):
790 790 merged.append(f)
791 791 else:
792 792 removed.append(f)
793 793 for f in removed:
794 794 repo.dirstate.remove(f)
795 795 for f in merged:
796 796 repo.dirstate.merge(f)
797 797 p1, p2 = repo.dirstate.parents()
798 798 repo.setparents(p1, merge)
799 799
800 800 match = scmutil.matchfiles(repo, files or [])
801 801 oldtip = repo['tip']
802 802 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
803 803 force=True)
804 804 if repo['tip'] == oldtip:
805 805 raise util.Abort(_("qpush exactly duplicates child changeset"))
806 806 if n is None:
807 807 raise util.Abort(_("repository commit failed"))
808 808
809 809 if update_status:
810 810 self.applied.append(statusentry(n, patchname))
811 811
812 812 if patcherr:
813 813 self.ui.warn(_("patch failed, rejects left in working dir\n"))
814 814 err = 2
815 815 break
816 816
817 817 if fuzz and strict:
818 818 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
819 819 err = 3
820 820 break
821 821 return (err, n)
822 822
823 823 def _cleanup(self, patches, numrevs, keep=False):
824 824 if not keep:
825 825 r = self.qrepo()
826 826 if r:
827 827 r[None].forget(patches)
828 828 for p in patches:
829 829 os.unlink(self.join(p))
830 830
831 831 qfinished = []
832 832 if numrevs:
833 833 qfinished = self.applied[:numrevs]
834 834 del self.applied[:numrevs]
835 835 self.applieddirty = True
836 836
837 837 unknown = []
838 838
839 839 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
840 840 reverse=True):
841 841 if i is not None:
842 842 del self.fullseries[i]
843 843 else:
844 844 unknown.append(p)
845 845
846 846 if unknown:
847 847 if numrevs:
848 848 rev = dict((entry.name, entry.node) for entry in qfinished)
849 849 for p in unknown:
850 850 msg = _('revision %s refers to unknown patches: %s\n')
851 851 self.ui.warn(msg % (short(rev[p]), p))
852 852 else:
853 853 msg = _('unknown patches: %s\n')
854 854 raise util.Abort(''.join(msg % p for p in unknown))
855 855
856 856 self.parseseries()
857 857 self.seriesdirty = True
858 858 return [entry.node for entry in qfinished]
859 859
860 860 def _revpatches(self, repo, revs):
861 861 firstrev = repo[self.applied[0].node].rev()
862 862 patches = []
863 863 for i, rev in enumerate(revs):
864 864
865 865 if rev < firstrev:
866 866 raise util.Abort(_('revision %d is not managed') % rev)
867 867
868 868 ctx = repo[rev]
869 869 base = self.applied[i].node
870 870 if ctx.node() != base:
871 871 msg = _('cannot delete revision %d above applied patches')
872 872 raise util.Abort(msg % rev)
873 873
874 874 patch = self.applied[i].name
875 875 for fmt in ('[mq]: %s', 'imported patch %s'):
876 876 if ctx.description() == fmt % patch:
877 877 msg = _('patch %s finalized without changeset message\n')
878 878 repo.ui.status(msg % patch)
879 879 break
880 880
881 881 patches.append(patch)
882 882 return patches
883 883
884 884 def finish(self, repo, revs):
885 885 # Manually trigger phase computation to ensure phasedefaults is
886 886 # executed before we remove the patches.
887 887 repo._phasecache
888 888 patches = self._revpatches(repo, sorted(revs))
889 889 qfinished = self._cleanup(patches, len(patches))
890 890 if qfinished and repo.ui.configbool('mq', 'secret', False):
891 891 # only use this logic when the secret option is added
892 892 oldqbase = repo[qfinished[0]]
893 893 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
894 894 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
895 895 phases.advanceboundary(repo, tphase, qfinished)
896 896
897 897 def delete(self, repo, patches, opts):
898 898 if not patches and not opts.get('rev'):
899 899 raise util.Abort(_('qdelete requires at least one revision or '
900 900 'patch name'))
901 901
902 902 realpatches = []
903 903 for patch in patches:
904 904 patch = self.lookup(patch, strict=True)
905 905 info = self.isapplied(patch)
906 906 if info:
907 907 raise util.Abort(_("cannot delete applied patch %s") % patch)
908 908 if patch not in self.series:
909 909 raise util.Abort(_("patch %s not in series file") % patch)
910 910 if patch not in realpatches:
911 911 realpatches.append(patch)
912 912
913 913 numrevs = 0
914 914 if opts.get('rev'):
915 915 if not self.applied:
916 916 raise util.Abort(_('no patches applied'))
917 917 revs = scmutil.revrange(repo, opts.get('rev'))
918 918 if len(revs) > 1 and revs[0] > revs[1]:
919 919 revs.reverse()
920 920 revpatches = self._revpatches(repo, revs)
921 921 realpatches += revpatches
922 922 numrevs = len(revpatches)
923 923
924 924 self._cleanup(realpatches, numrevs, opts.get('keep'))
925 925
926 926 def checktoppatch(self, repo):
927 927 if self.applied:
928 928 top = self.applied[-1].node
929 929 patch = self.applied[-1].name
930 930 pp = repo.dirstate.parents()
931 931 if top not in pp:
932 932 raise util.Abort(_("working directory revision is not qtip"))
933 933 return top, patch
934 934 return None, None
935 935
936 936 def checksubstate(self, repo, baserev=None):
937 937 '''return list of subrepos at a different revision than substate.
938 938 Abort if any subrepos have uncommitted changes.'''
939 939 inclsubs = []
940 940 wctx = repo[None]
941 941 if baserev:
942 942 bctx = repo[baserev]
943 943 else:
944 944 bctx = wctx.parents()[0]
945 945 for s in wctx.substate:
946 946 if wctx.sub(s).dirty(True):
947 947 raise util.Abort(
948 948 _("uncommitted changes in subrepository %s") % s)
949 949 elif s not in bctx.substate or bctx.sub(s).dirty():
950 950 inclsubs.append(s)
951 951 return inclsubs
952 952
953 953 def putsubstate2changes(self, substatestate, changes):
954 954 for files in changes[:3]:
955 955 if '.hgsubstate' in files:
956 956 return # already listed up
957 957 # not yet listed up
958 958 if substatestate in 'a?':
959 959 changes[1].append('.hgsubstate')
960 960 elif substatestate in 'r':
961 961 changes[2].append('.hgsubstate')
962 962 else: # modified
963 963 changes[0].append('.hgsubstate')
964 964
965 965 def localchangesfound(self, refresh=True):
966 966 if refresh:
967 967 raise util.Abort(_("local changes found, refresh first"))
968 968 else:
969 969 raise util.Abort(_("local changes found"))
970 970
971 971 def checklocalchanges(self, repo, force=False, refresh=True):
972 972 m, a, r, d = repo.status()[:4]
973 973 if (m or a or r or d) and not force:
974 974 self.localchangesfound(refresh)
975 975 return m, a, r, d
976 976
977 977 _reserved = ('series', 'status', 'guards', '.', '..')
978 978 def checkreservedname(self, name):
979 979 if name in self._reserved:
980 980 raise util.Abort(_('"%s" cannot be used as the name of a patch')
981 981 % name)
982 982 for prefix in ('.hg', '.mq'):
983 983 if name.startswith(prefix):
984 984 raise util.Abort(_('patch name cannot begin with "%s"')
985 985 % prefix)
986 986 for c in ('#', ':'):
987 987 if c in name:
988 988 raise util.Abort(_('"%s" cannot be used in the name of a patch')
989 989 % c)
990 990
991 991 def checkpatchname(self, name, force=False):
992 992 self.checkreservedname(name)
993 993 if not force and os.path.exists(self.join(name)):
994 994 if os.path.isdir(self.join(name)):
995 995 raise util.Abort(_('"%s" already exists as a directory')
996 996 % name)
997 997 else:
998 998 raise util.Abort(_('patch "%s" already exists') % name)
999 999
1000 1000 def checkkeepchanges(self, keepchanges, force):
1001 1001 if force and keepchanges:
1002 1002 raise util.Abort(_('cannot use both --force and --keep-changes'))
1003 1003
1004 1004 def new(self, repo, patchfn, *pats, **opts):
1005 1005 """options:
1006 1006 msg: a string or a no-argument function returning a string
1007 1007 """
1008 1008 msg = opts.get('msg')
1009 1009 user = opts.get('user')
1010 1010 date = opts.get('date')
1011 1011 if date:
1012 1012 date = util.parsedate(date)
1013 1013 diffopts = self.diffopts({'git': opts.get('git')})
1014 1014 if opts.get('checkname', True):
1015 1015 self.checkpatchname(patchfn)
1016 1016 inclsubs = self.checksubstate(repo)
1017 1017 if inclsubs:
1018 1018 inclsubs.append('.hgsubstate')
1019 1019 substatestate = repo.dirstate['.hgsubstate']
1020 1020 if opts.get('include') or opts.get('exclude') or pats:
1021 1021 if inclsubs:
1022 1022 pats = list(pats or []) + inclsubs
1023 1023 match = scmutil.match(repo[None], pats, opts)
1024 1024 # detect missing files in pats
1025 1025 def badfn(f, msg):
1026 1026 if f != '.hgsubstate': # .hgsubstate is auto-created
1027 1027 raise util.Abort('%s: %s' % (f, msg))
1028 1028 match.bad = badfn
1029 1029 changes = repo.status(match=match)
1030 1030 m, a, r, d = changes[:4]
1031 1031 else:
1032 1032 changes = self.checklocalchanges(repo, force=True)
1033 1033 m, a, r, d = changes
1034 1034 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1035 1035 if len(repo[None].parents()) > 1:
1036 1036 raise util.Abort(_('cannot manage merge changesets'))
1037 1037 commitfiles = m + a + r
1038 1038 self.checktoppatch(repo)
1039 1039 insert = self.fullseriesend()
1040 1040 wlock = repo.wlock()
1041 1041 try:
1042 1042 try:
1043 1043 # if patch file write fails, abort early
1044 1044 p = self.opener(patchfn, "w")
1045 1045 except IOError, e:
1046 1046 raise util.Abort(_('cannot write patch "%s": %s')
1047 1047 % (patchfn, e.strerror))
1048 1048 try:
1049 1049 if self.plainmode:
1050 1050 if user:
1051 1051 p.write("From: " + user + "\n")
1052 1052 if not date:
1053 1053 p.write("\n")
1054 1054 if date:
1055 1055 p.write("Date: %d %d\n\n" % date)
1056 1056 else:
1057 1057 p.write("# HG changeset patch\n")
1058 1058 p.write("# Parent "
1059 1059 + hex(repo[None].p1().node()) + "\n")
1060 1060 if user:
1061 1061 p.write("# User " + user + "\n")
1062 1062 if date:
1063 1063 p.write("# Date %s %s\n\n" % date)
1064 1064 if util.safehasattr(msg, '__call__'):
1065 1065 msg = msg()
1066 1066 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1067 1067 n = newcommit(repo, None, commitmsg, user, date, match=match,
1068 1068 force=True)
1069 1069 if n is None:
1070 1070 raise util.Abort(_("repo commit failed"))
1071 1071 try:
1072 1072 self.fullseries[insert:insert] = [patchfn]
1073 1073 self.applied.append(statusentry(n, patchfn))
1074 1074 self.parseseries()
1075 1075 self.seriesdirty = True
1076 1076 self.applieddirty = True
1077 1077 if msg:
1078 1078 msg = msg + "\n\n"
1079 1079 p.write(msg)
1080 1080 if commitfiles:
1081 1081 parent = self.qparents(repo, n)
1082 1082 if inclsubs:
1083 1083 self.putsubstate2changes(substatestate, changes)
1084 1084 chunks = patchmod.diff(repo, node1=parent, node2=n,
1085 1085 changes=changes, opts=diffopts)
1086 1086 for chunk in chunks:
1087 1087 p.write(chunk)
1088 1088 p.close()
1089 1089 r = self.qrepo()
1090 1090 if r:
1091 1091 r[None].add([patchfn])
1092 1092 except: # re-raises
1093 1093 repo.rollback()
1094 1094 raise
1095 1095 except Exception:
1096 1096 patchpath = self.join(patchfn)
1097 1097 try:
1098 1098 os.unlink(patchpath)
1099 1099 except OSError:
1100 1100 self.ui.warn(_('error unlinking %s\n') % patchpath)
1101 1101 raise
1102 1102 self.removeundo(repo)
1103 1103 finally:
1104 1104 release(wlock)
1105 1105
1106 1106 def strip(self, repo, revs, update=True, backup="all", force=None):
1107 1107 wlock = lock = None
1108 1108 try:
1109 1109 wlock = repo.wlock()
1110 1110 lock = repo.lock()
1111 1111
1112 1112 if update:
1113 1113 self.checklocalchanges(repo, force=force, refresh=False)
1114 1114 urev = self.qparents(repo, revs[0])
1115 1115 hg.clean(repo, urev)
1116 1116 repo.dirstate.write()
1117 1117
1118 1118 repair.strip(self.ui, repo, revs, backup)
1119 1119 finally:
1120 1120 release(lock, wlock)
1121 1121
1122 1122 def isapplied(self, patch):
1123 1123 """returns (index, rev, patch)"""
1124 1124 for i, a in enumerate(self.applied):
1125 1125 if a.name == patch:
1126 1126 return (i, a.node, a.name)
1127 1127 return None
1128 1128
1129 1129 # if the exact patch name does not exist, we try a few
1130 1130 # variations. If strict is passed, we try only #1
1131 1131 #
1132 1132 # 1) a number (as string) to indicate an offset in the series file
1133 1133 # 2) a unique substring of the patch name was given
1134 1134 # 3) patchname[-+]num to indicate an offset in the series file
1135 1135 def lookup(self, patch, strict=False):
1136 1136 def partialname(s):
1137 1137 if s in self.series:
1138 1138 return s
1139 1139 matches = [x for x in self.series if s in x]
1140 1140 if len(matches) > 1:
1141 1141 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1142 1142 for m in matches:
1143 1143 self.ui.warn(' %s\n' % m)
1144 1144 return None
1145 1145 if matches:
1146 1146 return matches[0]
1147 1147 if self.series and self.applied:
1148 1148 if s == 'qtip':
1149 1149 return self.series[self.seriesend(True)-1]
1150 1150 if s == 'qbase':
1151 1151 return self.series[0]
1152 1152 return None
1153 1153
1154 1154 if patch in self.series:
1155 1155 return patch
1156 1156
1157 1157 if not os.path.isfile(self.join(patch)):
1158 1158 try:
1159 1159 sno = int(patch)
1160 1160 except (ValueError, OverflowError):
1161 1161 pass
1162 1162 else:
1163 1163 if -len(self.series) <= sno < len(self.series):
1164 1164 return self.series[sno]
1165 1165
1166 1166 if not strict:
1167 1167 res = partialname(patch)
1168 1168 if res:
1169 1169 return res
1170 1170 minus = patch.rfind('-')
1171 1171 if minus >= 0:
1172 1172 res = partialname(patch[:minus])
1173 1173 if res:
1174 1174 i = self.series.index(res)
1175 1175 try:
1176 1176 off = int(patch[minus + 1:] or 1)
1177 1177 except (ValueError, OverflowError):
1178 1178 pass
1179 1179 else:
1180 1180 if i - off >= 0:
1181 1181 return self.series[i - off]
1182 1182 plus = patch.rfind('+')
1183 1183 if plus >= 0:
1184 1184 res = partialname(patch[:plus])
1185 1185 if res:
1186 1186 i = self.series.index(res)
1187 1187 try:
1188 1188 off = int(patch[plus + 1:] or 1)
1189 1189 except (ValueError, OverflowError):
1190 1190 pass
1191 1191 else:
1192 1192 if i + off < len(self.series):
1193 1193 return self.series[i + off]
1194 1194 raise util.Abort(_("patch %s not in series") % patch)
1195 1195
1196 1196 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1197 1197 all=False, move=False, exact=False, nobackup=False,
1198 1198 keepchanges=False):
1199 1199 self.checkkeepchanges(keepchanges, force)
1200 1200 diffopts = self.diffopts()
1201 1201 wlock = repo.wlock()
1202 1202 try:
1203 1203 heads = []
1204 1204 for b, ls in repo.branchmap().iteritems():
1205 1205 heads += ls
1206 1206 if not heads:
1207 1207 heads = [nullid]
1208 1208 if repo.dirstate.p1() not in heads and not exact:
1209 1209 self.ui.status(_("(working directory not at a head)\n"))
1210 1210
1211 1211 if not self.series:
1212 1212 self.ui.warn(_('no patches in series\n'))
1213 1213 return 0
1214 1214
1215 1215 # Suppose our series file is: A B C and the current 'top'
1216 1216 # patch is B. qpush C should be performed (moving forward)
1217 1217 # qpush B is a NOP (no change) qpush A is an error (can't
1218 1218 # go backwards with qpush)
1219 1219 if patch:
1220 1220 patch = self.lookup(patch)
1221 1221 info = self.isapplied(patch)
1222 1222 if info and info[0] >= len(self.applied) - 1:
1223 1223 self.ui.warn(
1224 1224 _('qpush: %s is already at the top\n') % patch)
1225 1225 return 0
1226 1226
1227 1227 pushable, reason = self.pushable(patch)
1228 1228 if pushable:
1229 1229 if self.series.index(patch) < self.seriesend():
1230 1230 raise util.Abort(
1231 1231 _("cannot push to a previous patch: %s") % patch)
1232 1232 else:
1233 1233 if reason:
1234 1234 reason = _('guarded by %s') % reason
1235 1235 else:
1236 1236 reason = _('no matching guards')
1237 1237 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1238 1238 return 1
1239 1239 elif all:
1240 1240 patch = self.series[-1]
1241 1241 if self.isapplied(patch):
1242 1242 self.ui.warn(_('all patches are currently applied\n'))
1243 1243 return 0
1244 1244
1245 1245 # Following the above example, starting at 'top' of B:
1246 1246 # qpush should be performed (pushes C), but a subsequent
1247 1247 # qpush without an argument is an error (nothing to
1248 1248 # apply). This allows a loop of "...while hg qpush..." to
1249 1249 # work as it detects an error when done
1250 1250 start = self.seriesend()
1251 1251 if start == len(self.series):
1252 1252 self.ui.warn(_('patch series already fully applied\n'))
1253 1253 return 1
1254 1254 if not force and not keepchanges:
1255 1255 self.checklocalchanges(repo, refresh=self.applied)
1256 1256
1257 1257 if exact:
1258 1258 if keepchanges:
1259 1259 raise util.Abort(
1260 1260 _("cannot use --exact and --keep-changes together"))
1261 1261 if move:
1262 1262 raise util.Abort(_('cannot use --exact and --move '
1263 1263 'together'))
1264 1264 if self.applied:
1265 1265 raise util.Abort(_('cannot push --exact with applied '
1266 1266 'patches'))
1267 1267 root = self.series[start]
1268 1268 target = patchheader(self.join(root), self.plainmode).parent
1269 1269 if not target:
1270 1270 raise util.Abort(
1271 1271 _("%s does not have a parent recorded") % root)
1272 1272 if not repo[target] == repo['.']:
1273 1273 hg.update(repo, target)
1274 1274
1275 1275 if move:
1276 1276 if not patch:
1277 1277 raise util.Abort(_("please specify the patch to move"))
1278 1278 for fullstart, rpn in enumerate(self.fullseries):
1279 1279 # strip markers for patch guards
1280 1280 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1281 1281 break
1282 1282 for i, rpn in enumerate(self.fullseries[fullstart:]):
1283 1283 # strip markers for patch guards
1284 1284 if self.guard_re.split(rpn, 1)[0] == patch:
1285 1285 break
1286 1286 index = fullstart + i
1287 1287 assert index < len(self.fullseries)
1288 1288 fullpatch = self.fullseries[index]
1289 1289 del self.fullseries[index]
1290 1290 self.fullseries.insert(fullstart, fullpatch)
1291 1291 self.parseseries()
1292 1292 self.seriesdirty = True
1293 1293
1294 1294 self.applieddirty = True
1295 1295 if start > 0:
1296 1296 self.checktoppatch(repo)
1297 1297 if not patch:
1298 1298 patch = self.series[start]
1299 1299 end = start + 1
1300 1300 else:
1301 1301 end = self.series.index(patch, start) + 1
1302 1302
1303 1303 tobackup = set()
1304 1304 if (not nobackup and force) or keepchanges:
1305 1305 m, a, r, d = self.checklocalchanges(repo, force=True)
1306 1306 if keepchanges:
1307 1307 tobackup.update(m + a + r + d)
1308 1308 else:
1309 1309 tobackup.update(m + a)
1310 1310
1311 1311 s = self.series[start:end]
1312 1312 all_files = set()
1313 1313 try:
1314 1314 if mergeq:
1315 1315 ret = self.mergepatch(repo, mergeq, s, diffopts)
1316 1316 else:
1317 1317 ret = self.apply(repo, s, list, all_files=all_files,
1318 1318 tobackup=tobackup, keepchanges=keepchanges)
1319 1319 except: # re-raises
1320 1320 self.ui.warn(_('cleaning up working directory...'))
1321 1321 node = repo.dirstate.p1()
1322 1322 hg.revert(repo, node, None)
1323 1323 # only remove unknown files that we know we touched or
1324 1324 # created while patching
1325 1325 for f in all_files:
1326 1326 if f not in repo.dirstate:
1327 1327 try:
1328 1328 util.unlinkpath(repo.wjoin(f))
1329 1329 except OSError, inst:
1330 1330 if inst.errno != errno.ENOENT:
1331 1331 raise
1332 1332 self.ui.warn(_('done\n'))
1333 1333 raise
1334 1334
1335 1335 if not self.applied:
1336 1336 return ret[0]
1337 1337 top = self.applied[-1].name
1338 1338 if ret[0] and ret[0] > 1:
1339 1339 msg = _("errors during apply, please fix and refresh %s\n")
1340 1340 self.ui.write(msg % top)
1341 1341 else:
1342 1342 self.ui.write(_("now at: %s\n") % top)
1343 1343 return ret[0]
1344 1344
1345 1345 finally:
1346 1346 wlock.release()
1347 1347
1348 1348 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 1349 nobackup=False, keepchanges=False):
1350 1350 self.checkkeepchanges(keepchanges, force)
1351 1351 wlock = repo.wlock()
1352 1352 try:
1353 1353 if patch:
1354 1354 # index, rev, patch
1355 1355 info = self.isapplied(patch)
1356 1356 if not info:
1357 1357 patch = self.lookup(patch)
1358 1358 info = self.isapplied(patch)
1359 1359 if not info:
1360 1360 raise util.Abort(_("patch %s is not applied") % patch)
1361 1361
1362 1362 if not self.applied:
1363 1363 # Allow qpop -a to work repeatedly,
1364 1364 # but not qpop without an argument
1365 1365 self.ui.warn(_("no patches applied\n"))
1366 1366 return not all
1367 1367
1368 1368 if all:
1369 1369 start = 0
1370 1370 elif patch:
1371 1371 start = info[0] + 1
1372 1372 else:
1373 1373 start = len(self.applied) - 1
1374 1374
1375 1375 if start >= len(self.applied):
1376 1376 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 1377 return
1378 1378
1379 1379 if not update:
1380 1380 parents = repo.dirstate.parents()
1381 1381 rr = [x.node for x in self.applied]
1382 1382 for p in parents:
1383 1383 if p in rr:
1384 1384 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 1385 update = True
1386 1386 else:
1387 1387 parents = [p.node() for p in repo[None].parents()]
1388 1388 needupdate = False
1389 1389 for entry in self.applied[start:]:
1390 1390 if entry.node in parents:
1391 1391 needupdate = True
1392 1392 break
1393 1393 update = needupdate
1394 1394
1395 1395 tobackup = set()
1396 1396 if update:
1397 1397 m, a, r, d = self.checklocalchanges(
1398 1398 repo, force=force or keepchanges)
1399 1399 if force:
1400 1400 if not nobackup:
1401 1401 tobackup.update(m + a)
1402 1402 elif keepchanges:
1403 1403 tobackup.update(m + a + r + d)
1404 1404
1405 1405 self.applieddirty = True
1406 1406 end = len(self.applied)
1407 1407 rev = self.applied[start].node
1408 1408 if update:
1409 1409 top = self.checktoppatch(repo)[0]
1410 1410
1411 1411 try:
1412 1412 heads = repo.changelog.heads(rev)
1413 1413 except error.LookupError:
1414 1414 node = short(rev)
1415 1415 raise util.Abort(_('trying to pop unknown node %s') % node)
1416 1416
1417 1417 if heads != [self.applied[-1].node]:
1418 1418 raise util.Abort(_("popping would remove a revision not "
1419 1419 "managed by this patch queue"))
1420 1420 if not repo[self.applied[-1].node].mutable():
1421 1421 raise util.Abort(
1422 1422 _("popping would remove an immutable revision"),
1423 1423 hint=_('see "hg help phases" for details'))
1424 1424
1425 1425 # we know there are no local changes, so we can make a simplified
1426 1426 # form of hg.update.
1427 1427 if update:
1428 1428 qp = self.qparents(repo, rev)
1429 1429 ctx = repo[qp]
1430 1430 m, a, r, d = repo.status(qp, top)[:4]
1431 1431 if d:
1432 1432 raise util.Abort(_("deletions found between repo revs"))
1433 1433
1434 1434 tobackup = set(a + m + r) & tobackup
1435 1435 if keepchanges and tobackup:
1436 1436 self.localchangesfound()
1437 1437 self.backup(repo, tobackup)
1438 1438
1439 1439 for f in a:
1440 1440 try:
1441 1441 util.unlinkpath(repo.wjoin(f))
1442 1442 except OSError, e:
1443 1443 if e.errno != errno.ENOENT:
1444 1444 raise
1445 1445 repo.dirstate.drop(f)
1446 1446 for f in m + r:
1447 1447 fctx = ctx[f]
1448 1448 repo.wwrite(f, fctx.data(), fctx.flags())
1449 1449 repo.dirstate.normal(f)
1450 1450 repo.setparents(qp, nullid)
1451 1451 for patch in reversed(self.applied[start:end]):
1452 1452 self.ui.status(_("popping %s\n") % patch.name)
1453 1453 del self.applied[start:end]
1454 1454 self.strip(repo, [rev], update=False, backup='strip')
1455 1455 if self.applied:
1456 1456 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1457 1457 else:
1458 1458 self.ui.write(_("patch queue now empty\n"))
1459 1459 finally:
1460 1460 wlock.release()
1461 1461
1462 1462 def diff(self, repo, pats, opts):
1463 1463 top, patch = self.checktoppatch(repo)
1464 1464 if not top:
1465 1465 self.ui.write(_("no patches applied\n"))
1466 1466 return
1467 1467 qp = self.qparents(repo, top)
1468 1468 if opts.get('reverse'):
1469 1469 node1, node2 = None, qp
1470 1470 else:
1471 1471 node1, node2 = qp, None
1472 1472 diffopts = self.diffopts(opts, patch)
1473 1473 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1474 1474
1475 1475 def refresh(self, repo, pats=None, **opts):
1476 1476 if not self.applied:
1477 1477 self.ui.write(_("no patches applied\n"))
1478 1478 return 1
1479 1479 msg = opts.get('msg', '').rstrip()
1480 1480 newuser = opts.get('user')
1481 1481 newdate = opts.get('date')
1482 1482 if newdate:
1483 1483 newdate = '%d %d' % util.parsedate(newdate)
1484 1484 wlock = repo.wlock()
1485 1485
1486 1486 try:
1487 1487 self.checktoppatch(repo)
1488 1488 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1489 1489 if repo.changelog.heads(top) != [top]:
1490 1490 raise util.Abort(_("cannot refresh a revision with children"))
1491 1491 if not repo[top].mutable():
1492 1492 raise util.Abort(_("cannot refresh immutable revision"),
1493 1493 hint=_('see "hg help phases" for details'))
1494 1494
1495 1495 cparents = repo.changelog.parents(top)
1496 1496 patchparent = self.qparents(repo, top)
1497 1497
1498 1498 inclsubs = self.checksubstate(repo, hex(patchparent))
1499 1499 if inclsubs:
1500 1500 inclsubs.append('.hgsubstate')
1501 1501 substatestate = repo.dirstate['.hgsubstate']
1502 1502
1503 1503 ph = patchheader(self.join(patchfn), self.plainmode)
1504 1504 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1505 1505 if msg:
1506 1506 ph.setmessage(msg)
1507 1507 if newuser:
1508 1508 ph.setuser(newuser)
1509 1509 if newdate:
1510 1510 ph.setdate(newdate)
1511 1511 ph.setparent(hex(patchparent))
1512 1512
1513 1513 # only commit new patch when write is complete
1514 1514 patchf = self.opener(patchfn, 'w', atomictemp=True)
1515 1515
1516 1516 comments = str(ph)
1517 1517 if comments:
1518 1518 patchf.write(comments)
1519 1519
1520 1520 # update the dirstate in place, strip off the qtip commit
1521 1521 # and then commit.
1522 1522 #
1523 1523 # this should really read:
1524 1524 # mm, dd, aa = repo.status(top, patchparent)[:3]
1525 # but we do it backwards to take advantage of manifest/chlog
1525 # but we do it backwards to take advantage of manifest/changelog
1526 1526 # caching against the next repo.status call
1527 1527 mm, aa, dd = repo.status(patchparent, top)[:3]
1528 1528 changes = repo.changelog.read(top)
1529 1529 man = repo.manifest.read(changes[0])
1530 1530 aaa = aa[:]
1531 1531 matchfn = scmutil.match(repo[None], pats, opts)
1532 1532 # in short mode, we only diff the files included in the
1533 1533 # patch already plus specified files
1534 1534 if opts.get('short'):
1535 1535 # if amending a patch, we start with existing
1536 1536 # files plus specified files - unfiltered
1537 1537 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1538 1538 # filter with include/exclude options
1539 1539 matchfn = scmutil.match(repo[None], opts=opts)
1540 1540 else:
1541 1541 match = scmutil.matchall(repo)
1542 1542 m, a, r, d = repo.status(match=match)[:4]
1543 1543 mm = set(mm)
1544 1544 aa = set(aa)
1545 1545 dd = set(dd)
1546 1546
1547 1547 # we might end up with files that were added between
1548 1548 # qtip and the dirstate parent, but then changed in the
1549 1549 # local dirstate. in this case, we want them to only
1550 1550 # show up in the added section
1551 1551 for x in m:
1552 1552 if x not in aa:
1553 1553 mm.add(x)
1554 1554 # we might end up with files added by the local dirstate that
1555 1555 # were deleted by the patch. In this case, they should only
1556 1556 # show up in the changed section.
1557 1557 for x in a:
1558 1558 if x in dd:
1559 1559 dd.remove(x)
1560 1560 mm.add(x)
1561 1561 else:
1562 1562 aa.add(x)
1563 1563 # make sure any files deleted in the local dirstate
1564 1564 # are not in the add or change column of the patch
1565 1565 forget = []
1566 1566 for x in d + r:
1567 1567 if x in aa:
1568 1568 aa.remove(x)
1569 1569 forget.append(x)
1570 1570 continue
1571 1571 else:
1572 1572 mm.discard(x)
1573 1573 dd.add(x)
1574 1574
1575 1575 m = list(mm)
1576 1576 r = list(dd)
1577 1577 a = list(aa)
1578 1578 c = [filter(matchfn, l) for l in (m, a, r)]
1579 1579 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1580 1580
1581 1581 try:
1582 1582 if diffopts.git or diffopts.upgrade:
1583 1583 copies = {}
1584 1584 for dst in a:
1585 1585 src = repo.dirstate.copied(dst)
1586 1586 # during qfold, the source file for copies may
1587 1587 # be removed. Treat this as a simple add.
1588 1588 if src is not None and src in repo.dirstate:
1589 1589 copies.setdefault(src, []).append(dst)
1590 1590 repo.dirstate.add(dst)
1591 1591 # remember the copies between patchparent and qtip
1592 1592 for dst in aaa:
1593 1593 f = repo.file(dst)
1594 1594 src = f.renamed(man[dst])
1595 1595 if src:
1596 1596 copies.setdefault(src[0], []).extend(
1597 1597 copies.get(dst, []))
1598 1598 if dst in a:
1599 1599 copies[src[0]].append(dst)
1600 1600 # we can't copy a file created by the patch itself
1601 1601 if dst in copies:
1602 1602 del copies[dst]
1603 1603 for src, dsts in copies.iteritems():
1604 1604 for dst in dsts:
1605 1605 repo.dirstate.copy(src, dst)
1606 1606 else:
1607 1607 for dst in a:
1608 1608 repo.dirstate.add(dst)
1609 1609 # Drop useless copy information
1610 1610 for f in list(repo.dirstate.copies()):
1611 1611 repo.dirstate.copy(None, f)
1612 1612 for f in r:
1613 1613 repo.dirstate.remove(f)
1614 1614 # if the patch excludes a modified file, mark that
1615 1615 # file with mtime=0 so status can see it.
1616 1616 mm = []
1617 1617 for i in xrange(len(m)-1, -1, -1):
1618 1618 if not matchfn(m[i]):
1619 1619 mm.append(m[i])
1620 1620 del m[i]
1621 1621 for f in m:
1622 1622 repo.dirstate.normal(f)
1623 1623 for f in mm:
1624 1624 repo.dirstate.normallookup(f)
1625 1625 for f in forget:
1626 1626 repo.dirstate.drop(f)
1627 1627
1628 1628 if not msg:
1629 1629 if not ph.message:
1630 1630 message = "[mq]: %s\n" % patchfn
1631 1631 else:
1632 1632 message = "\n".join(ph.message)
1633 1633 else:
1634 1634 message = msg
1635 1635
1636 1636 user = ph.user or changes[1]
1637 1637
1638 1638 oldphase = repo[top].phase()
1639 1639
1640 1640 # assumes strip can roll itself back if interrupted
1641 1641 repo.setparents(*cparents)
1642 1642 self.applied.pop()
1643 1643 self.applieddirty = True
1644 1644 self.strip(repo, [top], update=False,
1645 1645 backup='strip')
1646 1646 except: # re-raises
1647 1647 repo.dirstate.invalidate()
1648 1648 raise
1649 1649
1650 1650 try:
1651 1651 # might be nice to attempt to roll back strip after this
1652 1652
1653 1653 # Ensure we create a new changeset in the same phase than
1654 1654 # the old one.
1655 1655 n = newcommit(repo, oldphase, message, user, ph.date,
1656 1656 match=match, force=True)
1657 1657 # only write patch after a successful commit
1658 1658 if inclsubs:
1659 1659 self.putsubstate2changes(substatestate, c)
1660 1660 chunks = patchmod.diff(repo, patchparent,
1661 1661 changes=c, opts=diffopts)
1662 1662 for chunk in chunks:
1663 1663 patchf.write(chunk)
1664 1664 patchf.close()
1665 1665 self.applied.append(statusentry(n, patchfn))
1666 1666 except: # re-raises
1667 1667 ctx = repo[cparents[0]]
1668 1668 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1669 1669 self.savedirty()
1670 1670 self.ui.warn(_('refresh interrupted while patch was popped! '
1671 1671 '(revert --all, qpush to recover)\n'))
1672 1672 raise
1673 1673 finally:
1674 1674 wlock.release()
1675 1675 self.removeundo(repo)
1676 1676
1677 1677 def init(self, repo, create=False):
1678 1678 if not create and os.path.isdir(self.path):
1679 1679 raise util.Abort(_("patch queue directory already exists"))
1680 1680 try:
1681 1681 os.mkdir(self.path)
1682 1682 except OSError, inst:
1683 1683 if inst.errno != errno.EEXIST or not create:
1684 1684 raise
1685 1685 if create:
1686 1686 return self.qrepo(create=True)
1687 1687
1688 1688 def unapplied(self, repo, patch=None):
1689 1689 if patch and patch not in self.series:
1690 1690 raise util.Abort(_("patch %s is not in series file") % patch)
1691 1691 if not patch:
1692 1692 start = self.seriesend()
1693 1693 else:
1694 1694 start = self.series.index(patch) + 1
1695 1695 unapplied = []
1696 1696 for i in xrange(start, len(self.series)):
1697 1697 pushable, reason = self.pushable(i)
1698 1698 if pushable:
1699 1699 unapplied.append((i, self.series[i]))
1700 1700 self.explainpushable(i)
1701 1701 return unapplied
1702 1702
1703 1703 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1704 1704 summary=False):
1705 1705 def displayname(pfx, patchname, state):
1706 1706 if pfx:
1707 1707 self.ui.write(pfx)
1708 1708 if summary:
1709 1709 ph = patchheader(self.join(patchname), self.plainmode)
1710 1710 msg = ph.message and ph.message[0] or ''
1711 1711 if self.ui.formatted():
1712 1712 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1713 1713 if width > 0:
1714 1714 msg = util.ellipsis(msg, width)
1715 1715 else:
1716 1716 msg = ''
1717 1717 self.ui.write(patchname, label='qseries.' + state)
1718 1718 self.ui.write(': ')
1719 1719 self.ui.write(msg, label='qseries.message.' + state)
1720 1720 else:
1721 1721 self.ui.write(patchname, label='qseries.' + state)
1722 1722 self.ui.write('\n')
1723 1723
1724 1724 applied = set([p.name for p in self.applied])
1725 1725 if length is None:
1726 1726 length = len(self.series) - start
1727 1727 if not missing:
1728 1728 if self.ui.verbose:
1729 1729 idxwidth = len(str(start + length - 1))
1730 1730 for i in xrange(start, start + length):
1731 1731 patch = self.series[i]
1732 1732 if patch in applied:
1733 1733 char, state = 'A', 'applied'
1734 1734 elif self.pushable(i)[0]:
1735 1735 char, state = 'U', 'unapplied'
1736 1736 else:
1737 1737 char, state = 'G', 'guarded'
1738 1738 pfx = ''
1739 1739 if self.ui.verbose:
1740 1740 pfx = '%*d %s ' % (idxwidth, i, char)
1741 1741 elif status and status != char:
1742 1742 continue
1743 1743 displayname(pfx, patch, state)
1744 1744 else:
1745 1745 msng_list = []
1746 1746 for root, dirs, files in os.walk(self.path):
1747 1747 d = root[len(self.path) + 1:]
1748 1748 for f in files:
1749 1749 fl = os.path.join(d, f)
1750 1750 if (fl not in self.series and
1751 1751 fl not in (self.statuspath, self.seriespath,
1752 1752 self.guardspath)
1753 1753 and not fl.startswith('.')):
1754 1754 msng_list.append(fl)
1755 1755 for x in sorted(msng_list):
1756 1756 pfx = self.ui.verbose and ('D ') or ''
1757 1757 displayname(pfx, x, 'missing')
1758 1758
1759 1759 def issaveline(self, l):
1760 1760 if l.name == '.hg.patches.save.line':
1761 1761 return True
1762 1762
1763 1763 def qrepo(self, create=False):
1764 1764 ui = self.ui.copy()
1765 1765 ui.setconfig('paths', 'default', '', overlay=False)
1766 1766 ui.setconfig('paths', 'default-push', '', overlay=False)
1767 1767 if create or os.path.isdir(self.join(".hg")):
1768 1768 return hg.repository(ui, path=self.path, create=create)
1769 1769
1770 1770 def restore(self, repo, rev, delete=None, qupdate=None):
1771 1771 desc = repo[rev].description().strip()
1772 1772 lines = desc.splitlines()
1773 1773 i = 0
1774 1774 datastart = None
1775 1775 series = []
1776 1776 applied = []
1777 1777 qpp = None
1778 1778 for i, line in enumerate(lines):
1779 1779 if line == 'Patch Data:':
1780 1780 datastart = i + 1
1781 1781 elif line.startswith('Dirstate:'):
1782 1782 l = line.rstrip()
1783 1783 l = l[10:].split(' ')
1784 1784 qpp = [bin(x) for x in l]
1785 1785 elif datastart is not None:
1786 1786 l = line.rstrip()
1787 1787 n, name = l.split(':', 1)
1788 1788 if n:
1789 1789 applied.append(statusentry(bin(n), name))
1790 1790 else:
1791 1791 series.append(l)
1792 1792 if datastart is None:
1793 1793 self.ui.warn(_("no saved patch data found\n"))
1794 1794 return 1
1795 1795 self.ui.warn(_("restoring status: %s\n") % lines[0])
1796 1796 self.fullseries = series
1797 1797 self.applied = applied
1798 1798 self.parseseries()
1799 1799 self.seriesdirty = True
1800 1800 self.applieddirty = True
1801 1801 heads = repo.changelog.heads()
1802 1802 if delete:
1803 1803 if rev not in heads:
1804 1804 self.ui.warn(_("save entry has children, leaving it alone\n"))
1805 1805 else:
1806 1806 self.ui.warn(_("removing save entry %s\n") % short(rev))
1807 1807 pp = repo.dirstate.parents()
1808 1808 if rev in pp:
1809 1809 update = True
1810 1810 else:
1811 1811 update = False
1812 1812 self.strip(repo, [rev], update=update, backup='strip')
1813 1813 if qpp:
1814 1814 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1815 1815 (short(qpp[0]), short(qpp[1])))
1816 1816 if qupdate:
1817 1817 self.ui.status(_("updating queue directory\n"))
1818 1818 r = self.qrepo()
1819 1819 if not r:
1820 1820 self.ui.warn(_("unable to load queue repository\n"))
1821 1821 return 1
1822 1822 hg.clean(r, qpp[0])
1823 1823
1824 1824 def save(self, repo, msg=None):
1825 1825 if not self.applied:
1826 1826 self.ui.warn(_("save: no patches applied, exiting\n"))
1827 1827 return 1
1828 1828 if self.issaveline(self.applied[-1]):
1829 1829 self.ui.warn(_("status is already saved\n"))
1830 1830 return 1
1831 1831
1832 1832 if not msg:
1833 1833 msg = _("hg patches saved state")
1834 1834 else:
1835 1835 msg = "hg patches: " + msg.rstrip('\r\n')
1836 1836 r = self.qrepo()
1837 1837 if r:
1838 1838 pp = r.dirstate.parents()
1839 1839 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1840 1840 msg += "\n\nPatch Data:\n"
1841 1841 msg += ''.join('%s\n' % x for x in self.applied)
1842 1842 msg += ''.join(':%s\n' % x for x in self.fullseries)
1843 1843 n = repo.commit(msg, force=True)
1844 1844 if not n:
1845 1845 self.ui.warn(_("repo commit failed\n"))
1846 1846 return 1
1847 1847 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1848 1848 self.applieddirty = True
1849 1849 self.removeundo(repo)
1850 1850
1851 1851 def fullseriesend(self):
1852 1852 if self.applied:
1853 1853 p = self.applied[-1].name
1854 1854 end = self.findseries(p)
1855 1855 if end is None:
1856 1856 return len(self.fullseries)
1857 1857 return end + 1
1858 1858 return 0
1859 1859
1860 1860 def seriesend(self, all_patches=False):
1861 1861 """If all_patches is False, return the index of the next pushable patch
1862 1862 in the series, or the series length. If all_patches is True, return the
1863 1863 index of the first patch past the last applied one.
1864 1864 """
1865 1865 end = 0
1866 1866 def next(start):
1867 1867 if all_patches or start >= len(self.series):
1868 1868 return start
1869 1869 for i in xrange(start, len(self.series)):
1870 1870 p, reason = self.pushable(i)
1871 1871 if p:
1872 1872 return i
1873 1873 self.explainpushable(i)
1874 1874 return len(self.series)
1875 1875 if self.applied:
1876 1876 p = self.applied[-1].name
1877 1877 try:
1878 1878 end = self.series.index(p)
1879 1879 except ValueError:
1880 1880 return 0
1881 1881 return next(end + 1)
1882 1882 return next(end)
1883 1883
1884 1884 def appliedname(self, index):
1885 1885 pname = self.applied[index].name
1886 1886 if not self.ui.verbose:
1887 1887 p = pname
1888 1888 else:
1889 1889 p = str(self.series.index(pname)) + " " + pname
1890 1890 return p
1891 1891
1892 1892 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1893 1893 force=None, git=False):
1894 1894 def checkseries(patchname):
1895 1895 if patchname in self.series:
1896 1896 raise util.Abort(_('patch %s is already in the series file')
1897 1897 % patchname)
1898 1898
1899 1899 if rev:
1900 1900 if files:
1901 1901 raise util.Abort(_('option "-r" not valid when importing '
1902 1902 'files'))
1903 1903 rev = scmutil.revrange(repo, rev)
1904 1904 rev.sort(reverse=True)
1905 1905 elif not files:
1906 1906 raise util.Abort(_('no files or revisions specified'))
1907 1907 if (len(files) > 1 or len(rev) > 1) and patchname:
1908 1908 raise util.Abort(_('option "-n" not valid when importing multiple '
1909 1909 'patches'))
1910 1910 imported = []
1911 1911 if rev:
1912 1912 # If mq patches are applied, we can only import revisions
1913 1913 # that form a linear path to qbase.
1914 1914 # Otherwise, they should form a linear path to a head.
1915 1915 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1916 1916 if len(heads) > 1:
1917 1917 raise util.Abort(_('revision %d is the root of more than one '
1918 1918 'branch') % rev[-1])
1919 1919 if self.applied:
1920 1920 base = repo.changelog.node(rev[0])
1921 1921 if base in [n.node for n in self.applied]:
1922 1922 raise util.Abort(_('revision %d is already managed')
1923 1923 % rev[0])
1924 1924 if heads != [self.applied[-1].node]:
1925 1925 raise util.Abort(_('revision %d is not the parent of '
1926 1926 'the queue') % rev[0])
1927 1927 base = repo.changelog.rev(self.applied[0].node)
1928 1928 lastparent = repo.changelog.parentrevs(base)[0]
1929 1929 else:
1930 1930 if heads != [repo.changelog.node(rev[0])]:
1931 1931 raise util.Abort(_('revision %d has unmanaged children')
1932 1932 % rev[0])
1933 1933 lastparent = None
1934 1934
1935 1935 diffopts = self.diffopts({'git': git})
1936 1936 for r in rev:
1937 1937 if not repo[r].mutable():
1938 1938 raise util.Abort(_('revision %d is not mutable') % r,
1939 1939 hint=_('see "hg help phases" for details'))
1940 1940 p1, p2 = repo.changelog.parentrevs(r)
1941 1941 n = repo.changelog.node(r)
1942 1942 if p2 != nullrev:
1943 1943 raise util.Abort(_('cannot import merge revision %d') % r)
1944 1944 if lastparent and lastparent != r:
1945 1945 raise util.Abort(_('revision %d is not the parent of %d')
1946 1946 % (r, lastparent))
1947 1947 lastparent = p1
1948 1948
1949 1949 if not patchname:
1950 1950 patchname = normname('%d.diff' % r)
1951 1951 checkseries(patchname)
1952 1952 self.checkpatchname(patchname, force)
1953 1953 self.fullseries.insert(0, patchname)
1954 1954
1955 1955 patchf = self.opener(patchname, "w")
1956 1956 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1957 1957 patchf.close()
1958 1958
1959 1959 se = statusentry(n, patchname)
1960 1960 self.applied.insert(0, se)
1961 1961
1962 1962 self.added.append(patchname)
1963 1963 imported.append(patchname)
1964 1964 patchname = None
1965 1965 if rev and repo.ui.configbool('mq', 'secret', False):
1966 1966 # if we added anything with --rev, we must move the secret root
1967 1967 phases.retractboundary(repo, phases.secret, [n])
1968 1968 self.parseseries()
1969 1969 self.applieddirty = True
1970 1970 self.seriesdirty = True
1971 1971
1972 1972 for i, filename in enumerate(files):
1973 1973 if existing:
1974 1974 if filename == '-':
1975 1975 raise util.Abort(_('-e is incompatible with import from -'))
1976 1976 filename = normname(filename)
1977 1977 self.checkreservedname(filename)
1978 1978 originpath = self.join(filename)
1979 1979 if not os.path.isfile(originpath):
1980 1980 raise util.Abort(_("patch %s does not exist") % filename)
1981 1981
1982 1982 if patchname:
1983 1983 self.checkpatchname(patchname, force)
1984 1984
1985 1985 self.ui.write(_('renaming %s to %s\n')
1986 1986 % (filename, patchname))
1987 1987 util.rename(originpath, self.join(patchname))
1988 1988 else:
1989 1989 patchname = filename
1990 1990
1991 1991 else:
1992 1992 if filename == '-' and not patchname:
1993 1993 raise util.Abort(_('need --name to import a patch from -'))
1994 1994 elif not patchname:
1995 1995 patchname = normname(os.path.basename(filename.rstrip('/')))
1996 1996 self.checkpatchname(patchname, force)
1997 1997 try:
1998 1998 if filename == '-':
1999 1999 text = self.ui.fin.read()
2000 2000 else:
2001 2001 fp = url.open(self.ui, filename)
2002 2002 text = fp.read()
2003 2003 fp.close()
2004 2004 except (OSError, IOError):
2005 2005 raise util.Abort(_("unable to read file %s") % filename)
2006 2006 patchf = self.opener(patchname, "w")
2007 2007 patchf.write(text)
2008 2008 patchf.close()
2009 2009 if not force:
2010 2010 checkseries(patchname)
2011 2011 if patchname not in self.series:
2012 2012 index = self.fullseriesend() + i
2013 2013 self.fullseries[index:index] = [patchname]
2014 2014 self.parseseries()
2015 2015 self.seriesdirty = True
2016 2016 self.ui.warn(_("adding %s to series file\n") % patchname)
2017 2017 self.added.append(patchname)
2018 2018 imported.append(patchname)
2019 2019 patchname = None
2020 2020
2021 2021 self.removeundo(repo)
2022 2022 return imported
2023 2023
2024 2024 def fixkeepchangesopts(ui, opts):
2025 2025 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2026 2026 or opts.get('exact')):
2027 2027 return opts
2028 2028 opts = dict(opts)
2029 2029 opts['keep_changes'] = True
2030 2030 return opts
2031 2031
2032 2032 @command("qdelete|qremove|qrm",
2033 2033 [('k', 'keep', None, _('keep patch file')),
2034 2034 ('r', 'rev', [],
2035 2035 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2036 2036 _('hg qdelete [-k] [PATCH]...'))
2037 2037 def delete(ui, repo, *patches, **opts):
2038 2038 """remove patches from queue
2039 2039
2040 2040 The patches must not be applied, and at least one patch is required. Exact
2041 2041 patch identifiers must be given. With -k/--keep, the patch files are
2042 2042 preserved in the patch directory.
2043 2043
2044 2044 To stop managing a patch and move it into permanent history,
2045 2045 use the :hg:`qfinish` command."""
2046 2046 q = repo.mq
2047 2047 q.delete(repo, patches, opts)
2048 2048 q.savedirty()
2049 2049 return 0
2050 2050
2051 2051 @command("qapplied",
2052 2052 [('1', 'last', None, _('show only the preceding applied patch'))
2053 2053 ] + seriesopts,
2054 2054 _('hg qapplied [-1] [-s] [PATCH]'))
2055 2055 def applied(ui, repo, patch=None, **opts):
2056 2056 """print the patches already applied
2057 2057
2058 2058 Returns 0 on success."""
2059 2059
2060 2060 q = repo.mq
2061 2061
2062 2062 if patch:
2063 2063 if patch not in q.series:
2064 2064 raise util.Abort(_("patch %s is not in series file") % patch)
2065 2065 end = q.series.index(patch) + 1
2066 2066 else:
2067 2067 end = q.seriesend(True)
2068 2068
2069 2069 if opts.get('last') and not end:
2070 2070 ui.write(_("no patches applied\n"))
2071 2071 return 1
2072 2072 elif opts.get('last') and end == 1:
2073 2073 ui.write(_("only one patch applied\n"))
2074 2074 return 1
2075 2075 elif opts.get('last'):
2076 2076 start = end - 2
2077 2077 end = 1
2078 2078 else:
2079 2079 start = 0
2080 2080
2081 2081 q.qseries(repo, length=end, start=start, status='A',
2082 2082 summary=opts.get('summary'))
2083 2083
2084 2084
2085 2085 @command("qunapplied",
2086 2086 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2087 2087 _('hg qunapplied [-1] [-s] [PATCH]'))
2088 2088 def unapplied(ui, repo, patch=None, **opts):
2089 2089 """print the patches not yet applied
2090 2090
2091 2091 Returns 0 on success."""
2092 2092
2093 2093 q = repo.mq
2094 2094 if patch:
2095 2095 if patch not in q.series:
2096 2096 raise util.Abort(_("patch %s is not in series file") % patch)
2097 2097 start = q.series.index(patch) + 1
2098 2098 else:
2099 2099 start = q.seriesend(True)
2100 2100
2101 2101 if start == len(q.series) and opts.get('first'):
2102 2102 ui.write(_("all patches applied\n"))
2103 2103 return 1
2104 2104
2105 2105 length = opts.get('first') and 1 or None
2106 2106 q.qseries(repo, start=start, length=length, status='U',
2107 2107 summary=opts.get('summary'))
2108 2108
2109 2109 @command("qimport",
2110 2110 [('e', 'existing', None, _('import file in patch directory')),
2111 2111 ('n', 'name', '',
2112 2112 _('name of patch file'), _('NAME')),
2113 2113 ('f', 'force', None, _('overwrite existing files')),
2114 2114 ('r', 'rev', [],
2115 2115 _('place existing revisions under mq control'), _('REV')),
2116 2116 ('g', 'git', None, _('use git extended diff format')),
2117 2117 ('P', 'push', None, _('qpush after importing'))],
2118 2118 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2119 2119 def qimport(ui, repo, *filename, **opts):
2120 2120 """import a patch or existing changeset
2121 2121
2122 2122 The patch is inserted into the series after the last applied
2123 2123 patch. If no patches have been applied, qimport prepends the patch
2124 2124 to the series.
2125 2125
2126 2126 The patch will have the same name as its source file unless you
2127 2127 give it a new one with -n/--name.
2128 2128
2129 2129 You can register an existing patch inside the patch directory with
2130 2130 the -e/--existing flag.
2131 2131
2132 2132 With -f/--force, an existing patch of the same name will be
2133 2133 overwritten.
2134 2134
2135 2135 An existing changeset may be placed under mq control with -r/--rev
2136 2136 (e.g. qimport --rev tip -n patch will place tip under mq control).
2137 2137 With -g/--git, patches imported with --rev will use the git diff
2138 2138 format. See the diffs help topic for information on why this is
2139 2139 important for preserving rename/copy information and permission
2140 2140 changes. Use :hg:`qfinish` to remove changesets from mq control.
2141 2141
2142 2142 To import a patch from standard input, pass - as the patch file.
2143 2143 When importing from standard input, a patch name must be specified
2144 2144 using the --name flag.
2145 2145
2146 2146 To import an existing patch while renaming it::
2147 2147
2148 2148 hg qimport -e existing-patch -n new-name
2149 2149
2150 2150 Returns 0 if import succeeded.
2151 2151 """
2152 2152 lock = repo.lock() # cause this may move phase
2153 2153 try:
2154 2154 q = repo.mq
2155 2155 try:
2156 2156 imported = q.qimport(
2157 2157 repo, filename, patchname=opts.get('name'),
2158 2158 existing=opts.get('existing'), force=opts.get('force'),
2159 2159 rev=opts.get('rev'), git=opts.get('git'))
2160 2160 finally:
2161 2161 q.savedirty()
2162 2162 finally:
2163 2163 lock.release()
2164 2164
2165 2165 if imported and opts.get('push') and not opts.get('rev'):
2166 2166 return q.push(repo, imported[-1])
2167 2167 return 0
2168 2168
2169 2169 def qinit(ui, repo, create):
2170 2170 """initialize a new queue repository
2171 2171
2172 2172 This command also creates a series file for ordering patches, and
2173 2173 an mq-specific .hgignore file in the queue repository, to exclude
2174 2174 the status and guards files (these contain mostly transient state).
2175 2175
2176 2176 Returns 0 if initialization succeeded."""
2177 2177 q = repo.mq
2178 2178 r = q.init(repo, create)
2179 2179 q.savedirty()
2180 2180 if r:
2181 2181 if not os.path.exists(r.wjoin('.hgignore')):
2182 2182 fp = r.wopener('.hgignore', 'w')
2183 2183 fp.write('^\\.hg\n')
2184 2184 fp.write('^\\.mq\n')
2185 2185 fp.write('syntax: glob\n')
2186 2186 fp.write('status\n')
2187 2187 fp.write('guards\n')
2188 2188 fp.close()
2189 2189 if not os.path.exists(r.wjoin('series')):
2190 2190 r.wopener('series', 'w').close()
2191 2191 r[None].add(['.hgignore', 'series'])
2192 2192 commands.add(ui, r)
2193 2193 return 0
2194 2194
2195 2195 @command("^qinit",
2196 2196 [('c', 'create-repo', None, _('create queue repository'))],
2197 2197 _('hg qinit [-c]'))
2198 2198 def init(ui, repo, **opts):
2199 2199 """init a new queue repository (DEPRECATED)
2200 2200
2201 2201 The queue repository is unversioned by default. If
2202 2202 -c/--create-repo is specified, qinit will create a separate nested
2203 2203 repository for patches (qinit -c may also be run later to convert
2204 2204 an unversioned patch repository into a versioned one). You can use
2205 2205 qcommit to commit changes to this queue repository.
2206 2206
2207 2207 This command is deprecated. Without -c, it's implied by other relevant
2208 2208 commands. With -c, use :hg:`init --mq` instead."""
2209 2209 return qinit(ui, repo, create=opts.get('create_repo'))
2210 2210
2211 2211 @command("qclone",
2212 2212 [('', 'pull', None, _('use pull protocol to copy metadata')),
2213 2213 ('U', 'noupdate', None,
2214 2214 _('do not update the new working directories')),
2215 2215 ('', 'uncompressed', None,
2216 2216 _('use uncompressed transfer (fast over LAN)')),
2217 2217 ('p', 'patches', '',
2218 2218 _('location of source patch repository'), _('REPO')),
2219 2219 ] + commands.remoteopts,
2220 2220 _('hg qclone [OPTION]... SOURCE [DEST]'))
2221 2221 def clone(ui, source, dest=None, **opts):
2222 2222 '''clone main and patch repository at same time
2223 2223
2224 2224 If source is local, destination will have no patches applied. If
2225 2225 source is remote, this command can not check if patches are
2226 2226 applied in source, so cannot guarantee that patches are not
2227 2227 applied in destination. If you clone remote repository, be sure
2228 2228 before that it has no patches applied.
2229 2229
2230 2230 Source patch repository is looked for in <src>/.hg/patches by
2231 2231 default. Use -p <url> to change.
2232 2232
2233 2233 The patch directory must be a nested Mercurial repository, as
2234 2234 would be created by :hg:`init --mq`.
2235 2235
2236 2236 Return 0 on success.
2237 2237 '''
2238 2238 def patchdir(repo):
2239 2239 """compute a patch repo url from a repo object"""
2240 2240 url = repo.url()
2241 2241 if url.endswith('/'):
2242 2242 url = url[:-1]
2243 2243 return url + '/.hg/patches'
2244 2244
2245 2245 # main repo (destination and sources)
2246 2246 if dest is None:
2247 2247 dest = hg.defaultdest(source)
2248 2248 sr = hg.peer(ui, opts, ui.expandpath(source))
2249 2249
2250 2250 # patches repo (source only)
2251 2251 if opts.get('patches'):
2252 2252 patchespath = ui.expandpath(opts.get('patches'))
2253 2253 else:
2254 2254 patchespath = patchdir(sr)
2255 2255 try:
2256 2256 hg.peer(ui, opts, patchespath)
2257 2257 except error.RepoError:
2258 2258 raise util.Abort(_('versioned patch repository not found'
2259 2259 ' (see init --mq)'))
2260 2260 qbase, destrev = None, None
2261 2261 if sr.local():
2262 2262 repo = sr.local()
2263 2263 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2264 2264 qbase = repo.mq.applied[0].node
2265 2265 if not hg.islocal(dest):
2266 2266 heads = set(repo.heads())
2267 2267 destrev = list(heads.difference(repo.heads(qbase)))
2268 2268 destrev.append(repo.changelog.parents(qbase)[0])
2269 2269 elif sr.capable('lookup'):
2270 2270 try:
2271 2271 qbase = sr.lookup('qbase')
2272 2272 except error.RepoError:
2273 2273 pass
2274 2274
2275 2275 ui.note(_('cloning main repository\n'))
2276 2276 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2277 2277 pull=opts.get('pull'),
2278 2278 rev=destrev,
2279 2279 update=False,
2280 2280 stream=opts.get('uncompressed'))
2281 2281
2282 2282 ui.note(_('cloning patch repository\n'))
2283 2283 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2284 2284 pull=opts.get('pull'), update=not opts.get('noupdate'),
2285 2285 stream=opts.get('uncompressed'))
2286 2286
2287 2287 if dr.local():
2288 2288 repo = dr.local()
2289 2289 if qbase:
2290 2290 ui.note(_('stripping applied patches from destination '
2291 2291 'repository\n'))
2292 2292 repo.mq.strip(repo, [qbase], update=False, backup=None)
2293 2293 if not opts.get('noupdate'):
2294 2294 ui.note(_('updating destination repository\n'))
2295 2295 hg.update(repo, repo.changelog.tip())
2296 2296
2297 2297 @command("qcommit|qci",
2298 2298 commands.table["^commit|ci"][1],
2299 2299 _('hg qcommit [OPTION]... [FILE]...'))
2300 2300 def commit(ui, repo, *pats, **opts):
2301 2301 """commit changes in the queue repository (DEPRECATED)
2302 2302
2303 2303 This command is deprecated; use :hg:`commit --mq` instead."""
2304 2304 q = repo.mq
2305 2305 r = q.qrepo()
2306 2306 if not r:
2307 2307 raise util.Abort('no queue repository')
2308 2308 commands.commit(r.ui, r, *pats, **opts)
2309 2309
2310 2310 @command("qseries",
2311 2311 [('m', 'missing', None, _('print patches not in series')),
2312 2312 ] + seriesopts,
2313 2313 _('hg qseries [-ms]'))
2314 2314 def series(ui, repo, **opts):
2315 2315 """print the entire series file
2316 2316
2317 2317 Returns 0 on success."""
2318 2318 repo.mq.qseries(repo, missing=opts.get('missing'),
2319 2319 summary=opts.get('summary'))
2320 2320 return 0
2321 2321
2322 2322 @command("qtop", seriesopts, _('hg qtop [-s]'))
2323 2323 def top(ui, repo, **opts):
2324 2324 """print the name of the current patch
2325 2325
2326 2326 Returns 0 on success."""
2327 2327 q = repo.mq
2328 2328 t = q.applied and q.seriesend(True) or 0
2329 2329 if t:
2330 2330 q.qseries(repo, start=t - 1, length=1, status='A',
2331 2331 summary=opts.get('summary'))
2332 2332 else:
2333 2333 ui.write(_("no patches applied\n"))
2334 2334 return 1
2335 2335
2336 2336 @command("qnext", seriesopts, _('hg qnext [-s]'))
2337 2337 def next(ui, repo, **opts):
2338 2338 """print the name of the next pushable patch
2339 2339
2340 2340 Returns 0 on success."""
2341 2341 q = repo.mq
2342 2342 end = q.seriesend()
2343 2343 if end == len(q.series):
2344 2344 ui.write(_("all patches applied\n"))
2345 2345 return 1
2346 2346 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2347 2347
2348 2348 @command("qprev", seriesopts, _('hg qprev [-s]'))
2349 2349 def prev(ui, repo, **opts):
2350 2350 """print the name of the preceding applied patch
2351 2351
2352 2352 Returns 0 on success."""
2353 2353 q = repo.mq
2354 2354 l = len(q.applied)
2355 2355 if l == 1:
2356 2356 ui.write(_("only one patch applied\n"))
2357 2357 return 1
2358 2358 if not l:
2359 2359 ui.write(_("no patches applied\n"))
2360 2360 return 1
2361 2361 idx = q.series.index(q.applied[-2].name)
2362 2362 q.qseries(repo, start=idx, length=1, status='A',
2363 2363 summary=opts.get('summary'))
2364 2364
2365 2365 def setupheaderopts(ui, opts):
2366 2366 if not opts.get('user') and opts.get('currentuser'):
2367 2367 opts['user'] = ui.username()
2368 2368 if not opts.get('date') and opts.get('currentdate'):
2369 2369 opts['date'] = "%d %d" % util.makedate()
2370 2370
2371 2371 @command("^qnew",
2372 2372 [('e', 'edit', None, _('edit commit message')),
2373 2373 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2374 2374 ('g', 'git', None, _('use git extended diff format')),
2375 2375 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2376 2376 ('u', 'user', '',
2377 2377 _('add "From: <USER>" to patch'), _('USER')),
2378 2378 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2379 2379 ('d', 'date', '',
2380 2380 _('add "Date: <DATE>" to patch'), _('DATE'))
2381 2381 ] + commands.walkopts + commands.commitopts,
2382 2382 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2383 2383 def new(ui, repo, patch, *args, **opts):
2384 2384 """create a new patch
2385 2385
2386 2386 qnew creates a new patch on top of the currently-applied patch (if
2387 2387 any). The patch will be initialized with any outstanding changes
2388 2388 in the working directory. You may also use -I/--include,
2389 2389 -X/--exclude, and/or a list of files after the patch name to add
2390 2390 only changes to matching files to the new patch, leaving the rest
2391 2391 as uncommitted modifications.
2392 2392
2393 2393 -u/--user and -d/--date can be used to set the (given) user and
2394 2394 date, respectively. -U/--currentuser and -D/--currentdate set user
2395 2395 to current user and date to current date.
2396 2396
2397 2397 -e/--edit, -m/--message or -l/--logfile set the patch header as
2398 2398 well as the commit message. If none is specified, the header is
2399 2399 empty and the commit message is '[mq]: PATCH'.
2400 2400
2401 2401 Use the -g/--git option to keep the patch in the git extended diff
2402 2402 format. Read the diffs help topic for more information on why this
2403 2403 is important for preserving permission changes and copy/rename
2404 2404 information.
2405 2405
2406 2406 Returns 0 on successful creation of a new patch.
2407 2407 """
2408 2408 msg = cmdutil.logmessage(ui, opts)
2409 2409 def getmsg():
2410 2410 return ui.edit(msg, opts.get('user') or ui.username())
2411 2411 q = repo.mq
2412 2412 opts['msg'] = msg
2413 2413 if opts.get('edit'):
2414 2414 opts['msg'] = getmsg
2415 2415 else:
2416 2416 opts['msg'] = msg
2417 2417 setupheaderopts(ui, opts)
2418 2418 q.new(repo, patch, *args, **opts)
2419 2419 q.savedirty()
2420 2420 return 0
2421 2421
2422 2422 @command("^qrefresh",
2423 2423 [('e', 'edit', None, _('edit commit message')),
2424 2424 ('g', 'git', None, _('use git extended diff format')),
2425 2425 ('s', 'short', None,
2426 2426 _('refresh only files already in the patch and specified files')),
2427 2427 ('U', 'currentuser', None,
2428 2428 _('add/update author field in patch with current user')),
2429 2429 ('u', 'user', '',
2430 2430 _('add/update author field in patch with given user'), _('USER')),
2431 2431 ('D', 'currentdate', None,
2432 2432 _('add/update date field in patch with current date')),
2433 2433 ('d', 'date', '',
2434 2434 _('add/update date field in patch with given date'), _('DATE'))
2435 2435 ] + commands.walkopts + commands.commitopts,
2436 2436 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2437 2437 def refresh(ui, repo, *pats, **opts):
2438 2438 """update the current patch
2439 2439
2440 2440 If any file patterns are provided, the refreshed patch will
2441 2441 contain only the modifications that match those patterns; the
2442 2442 remaining modifications will remain in the working directory.
2443 2443
2444 2444 If -s/--short is specified, files currently included in the patch
2445 2445 will be refreshed just like matched files and remain in the patch.
2446 2446
2447 2447 If -e/--edit is specified, Mercurial will start your configured editor for
2448 2448 you to enter a message. In case qrefresh fails, you will find a backup of
2449 2449 your message in ``.hg/last-message.txt``.
2450 2450
2451 2451 hg add/remove/copy/rename work as usual, though you might want to
2452 2452 use git-style patches (-g/--git or [diff] git=1) to track copies
2453 2453 and renames. See the diffs help topic for more information on the
2454 2454 git diff format.
2455 2455
2456 2456 Returns 0 on success.
2457 2457 """
2458 2458 q = repo.mq
2459 2459 message = cmdutil.logmessage(ui, opts)
2460 2460 if opts.get('edit'):
2461 2461 if not q.applied:
2462 2462 ui.write(_("no patches applied\n"))
2463 2463 return 1
2464 2464 if message:
2465 2465 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2466 2466 patch = q.applied[-1].name
2467 2467 ph = patchheader(q.join(patch), q.plainmode)
2468 2468 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2469 2469 # We don't want to lose the patch message if qrefresh fails (issue2062)
2470 2470 repo.savecommitmessage(message)
2471 2471 setupheaderopts(ui, opts)
2472 2472 wlock = repo.wlock()
2473 2473 try:
2474 2474 ret = q.refresh(repo, pats, msg=message, **opts)
2475 2475 q.savedirty()
2476 2476 return ret
2477 2477 finally:
2478 2478 wlock.release()
2479 2479
2480 2480 @command("^qdiff",
2481 2481 commands.diffopts + commands.diffopts2 + commands.walkopts,
2482 2482 _('hg qdiff [OPTION]... [FILE]...'))
2483 2483 def diff(ui, repo, *pats, **opts):
2484 2484 """diff of the current patch and subsequent modifications
2485 2485
2486 2486 Shows a diff which includes the current patch as well as any
2487 2487 changes which have been made in the working directory since the
2488 2488 last refresh (thus showing what the current patch would become
2489 2489 after a qrefresh).
2490 2490
2491 2491 Use :hg:`diff` if you only want to see the changes made since the
2492 2492 last qrefresh, or :hg:`export qtip` if you want to see changes
2493 2493 made by the current patch without including changes made since the
2494 2494 qrefresh.
2495 2495
2496 2496 Returns 0 on success.
2497 2497 """
2498 2498 repo.mq.diff(repo, pats, opts)
2499 2499 return 0
2500 2500
2501 2501 @command('qfold',
2502 2502 [('e', 'edit', None, _('edit patch header')),
2503 2503 ('k', 'keep', None, _('keep folded patch files')),
2504 2504 ] + commands.commitopts,
2505 2505 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2506 2506 def fold(ui, repo, *files, **opts):
2507 2507 """fold the named patches into the current patch
2508 2508
2509 2509 Patches must not yet be applied. Each patch will be successively
2510 2510 applied to the current patch in the order given. If all the
2511 2511 patches apply successfully, the current patch will be refreshed
2512 2512 with the new cumulative patch, and the folded patches will be
2513 2513 deleted. With -k/--keep, the folded patch files will not be
2514 2514 removed afterwards.
2515 2515
2516 2516 The header for each folded patch will be concatenated with the
2517 2517 current patch header, separated by a line of ``* * *``.
2518 2518
2519 2519 Returns 0 on success."""
2520 2520 q = repo.mq
2521 2521 if not files:
2522 2522 raise util.Abort(_('qfold requires at least one patch name'))
2523 2523 if not q.checktoppatch(repo)[0]:
2524 2524 raise util.Abort(_('no patches applied'))
2525 2525 q.checklocalchanges(repo)
2526 2526
2527 2527 message = cmdutil.logmessage(ui, opts)
2528 2528 if opts.get('edit'):
2529 2529 if message:
2530 2530 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2531 2531
2532 2532 parent = q.lookup('qtip')
2533 2533 patches = []
2534 2534 messages = []
2535 2535 for f in files:
2536 2536 p = q.lookup(f)
2537 2537 if p in patches or p == parent:
2538 2538 ui.warn(_('skipping already folded patch %s\n') % p)
2539 2539 if q.isapplied(p):
2540 2540 raise util.Abort(_('qfold cannot fold already applied patch %s')
2541 2541 % p)
2542 2542 patches.append(p)
2543 2543
2544 2544 for p in patches:
2545 2545 if not message:
2546 2546 ph = patchheader(q.join(p), q.plainmode)
2547 2547 if ph.message:
2548 2548 messages.append(ph.message)
2549 2549 pf = q.join(p)
2550 2550 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2551 2551 if not patchsuccess:
2552 2552 raise util.Abort(_('error folding patch %s') % p)
2553 2553
2554 2554 if not message:
2555 2555 ph = patchheader(q.join(parent), q.plainmode)
2556 2556 message, user = ph.message, ph.user
2557 2557 for msg in messages:
2558 2558 message.append('* * *')
2559 2559 message.extend(msg)
2560 2560 message = '\n'.join(message)
2561 2561
2562 2562 if opts.get('edit'):
2563 2563 message = ui.edit(message, user or ui.username())
2564 2564
2565 2565 diffopts = q.patchopts(q.diffopts(), *patches)
2566 2566 wlock = repo.wlock()
2567 2567 try:
2568 2568 q.refresh(repo, msg=message, git=diffopts.git)
2569 2569 q.delete(repo, patches, opts)
2570 2570 q.savedirty()
2571 2571 finally:
2572 2572 wlock.release()
2573 2573
2574 2574 @command("qgoto",
2575 2575 [('', 'keep-changes', None,
2576 2576 _('tolerate non-conflicting local changes')),
2577 2577 ('f', 'force', None, _('overwrite any local changes')),
2578 2578 ('', 'no-backup', None, _('do not save backup copies of files'))],
2579 2579 _('hg qgoto [OPTION]... PATCH'))
2580 2580 def goto(ui, repo, patch, **opts):
2581 2581 '''push or pop patches until named patch is at top of stack
2582 2582
2583 2583 Returns 0 on success.'''
2584 2584 opts = fixkeepchangesopts(ui, opts)
2585 2585 q = repo.mq
2586 2586 patch = q.lookup(patch)
2587 2587 nobackup = opts.get('no_backup')
2588 2588 keepchanges = opts.get('keep_changes')
2589 2589 if q.isapplied(patch):
2590 2590 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2591 2591 keepchanges=keepchanges)
2592 2592 else:
2593 2593 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2594 2594 keepchanges=keepchanges)
2595 2595 q.savedirty()
2596 2596 return ret
2597 2597
2598 2598 @command("qguard",
2599 2599 [('l', 'list', None, _('list all patches and guards')),
2600 2600 ('n', 'none', None, _('drop all guards'))],
2601 2601 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2602 2602 def guard(ui, repo, *args, **opts):
2603 2603 '''set or print guards for a patch
2604 2604
2605 2605 Guards control whether a patch can be pushed. A patch with no
2606 2606 guards is always pushed. A patch with a positive guard ("+foo") is
2607 2607 pushed only if the :hg:`qselect` command has activated it. A patch with
2608 2608 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2609 2609 has activated it.
2610 2610
2611 2611 With no arguments, print the currently active guards.
2612 2612 With arguments, set guards for the named patch.
2613 2613
2614 2614 .. note::
2615 2615 Specifying negative guards now requires '--'.
2616 2616
2617 2617 To set guards on another patch::
2618 2618
2619 2619 hg qguard other.patch -- +2.6.17 -stable
2620 2620
2621 2621 Returns 0 on success.
2622 2622 '''
2623 2623 def status(idx):
2624 2624 guards = q.seriesguards[idx] or ['unguarded']
2625 2625 if q.series[idx] in applied:
2626 2626 state = 'applied'
2627 2627 elif q.pushable(idx)[0]:
2628 2628 state = 'unapplied'
2629 2629 else:
2630 2630 state = 'guarded'
2631 2631 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2632 2632 ui.write('%s: ' % ui.label(q.series[idx], label))
2633 2633
2634 2634 for i, guard in enumerate(guards):
2635 2635 if guard.startswith('+'):
2636 2636 ui.write(guard, label='qguard.positive')
2637 2637 elif guard.startswith('-'):
2638 2638 ui.write(guard, label='qguard.negative')
2639 2639 else:
2640 2640 ui.write(guard, label='qguard.unguarded')
2641 2641 if i != len(guards) - 1:
2642 2642 ui.write(' ')
2643 2643 ui.write('\n')
2644 2644 q = repo.mq
2645 2645 applied = set(p.name for p in q.applied)
2646 2646 patch = None
2647 2647 args = list(args)
2648 2648 if opts.get('list'):
2649 2649 if args or opts.get('none'):
2650 2650 raise util.Abort(_('cannot mix -l/--list with options or '
2651 2651 'arguments'))
2652 2652 for i in xrange(len(q.series)):
2653 2653 status(i)
2654 2654 return
2655 2655 if not args or args[0][0:1] in '-+':
2656 2656 if not q.applied:
2657 2657 raise util.Abort(_('no patches applied'))
2658 2658 patch = q.applied[-1].name
2659 2659 if patch is None and args[0][0:1] not in '-+':
2660 2660 patch = args.pop(0)
2661 2661 if patch is None:
2662 2662 raise util.Abort(_('no patch to work with'))
2663 2663 if args or opts.get('none'):
2664 2664 idx = q.findseries(patch)
2665 2665 if idx is None:
2666 2666 raise util.Abort(_('no patch named %s') % patch)
2667 2667 q.setguards(idx, args)
2668 2668 q.savedirty()
2669 2669 else:
2670 2670 status(q.series.index(q.lookup(patch)))
2671 2671
2672 2672 @command("qheader", [], _('hg qheader [PATCH]'))
2673 2673 def header(ui, repo, patch=None):
2674 2674 """print the header of the topmost or specified patch
2675 2675
2676 2676 Returns 0 on success."""
2677 2677 q = repo.mq
2678 2678
2679 2679 if patch:
2680 2680 patch = q.lookup(patch)
2681 2681 else:
2682 2682 if not q.applied:
2683 2683 ui.write(_('no patches applied\n'))
2684 2684 return 1
2685 2685 patch = q.lookup('qtip')
2686 2686 ph = patchheader(q.join(patch), q.plainmode)
2687 2687
2688 2688 ui.write('\n'.join(ph.message) + '\n')
2689 2689
2690 2690 def lastsavename(path):
2691 2691 (directory, base) = os.path.split(path)
2692 2692 names = os.listdir(directory)
2693 2693 namere = re.compile("%s.([0-9]+)" % base)
2694 2694 maxindex = None
2695 2695 maxname = None
2696 2696 for f in names:
2697 2697 m = namere.match(f)
2698 2698 if m:
2699 2699 index = int(m.group(1))
2700 2700 if maxindex is None or index > maxindex:
2701 2701 maxindex = index
2702 2702 maxname = f
2703 2703 if maxname:
2704 2704 return (os.path.join(directory, maxname), maxindex)
2705 2705 return (None, None)
2706 2706
2707 2707 def savename(path):
2708 2708 (last, index) = lastsavename(path)
2709 2709 if last is None:
2710 2710 index = 0
2711 2711 newpath = path + ".%d" % (index + 1)
2712 2712 return newpath
2713 2713
2714 2714 @command("^qpush",
2715 2715 [('', 'keep-changes', None,
2716 2716 _('tolerate non-conflicting local changes')),
2717 2717 ('f', 'force', None, _('apply on top of local changes')),
2718 2718 ('e', 'exact', None,
2719 2719 _('apply the target patch to its recorded parent')),
2720 2720 ('l', 'list', None, _('list patch name in commit text')),
2721 2721 ('a', 'all', None, _('apply all patches')),
2722 2722 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2723 2723 ('n', 'name', '',
2724 2724 _('merge queue name (DEPRECATED)'), _('NAME')),
2725 2725 ('', 'move', None,
2726 2726 _('reorder patch series and apply only the patch')),
2727 2727 ('', 'no-backup', None, _('do not save backup copies of files'))],
2728 2728 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2729 2729 def push(ui, repo, patch=None, **opts):
2730 2730 """push the next patch onto the stack
2731 2731
2732 2732 By default, abort if the working directory contains uncommitted
2733 2733 changes. With --keep-changes, abort only if the uncommitted files
2734 2734 overlap with patched files. With -f/--force, backup and patch over
2735 2735 uncommitted changes.
2736 2736
2737 2737 Return 0 on success.
2738 2738 """
2739 2739 q = repo.mq
2740 2740 mergeq = None
2741 2741
2742 2742 opts = fixkeepchangesopts(ui, opts)
2743 2743 if opts.get('merge'):
2744 2744 if opts.get('name'):
2745 2745 newpath = repo.join(opts.get('name'))
2746 2746 else:
2747 2747 newpath, i = lastsavename(q.path)
2748 2748 if not newpath:
2749 2749 ui.warn(_("no saved queues found, please use -n\n"))
2750 2750 return 1
2751 2751 mergeq = queue(ui, repo.path, newpath)
2752 2752 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2753 2753 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2754 2754 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2755 2755 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2756 2756 keepchanges=opts.get('keep_changes'))
2757 2757 return ret
2758 2758
2759 2759 @command("^qpop",
2760 2760 [('a', 'all', None, _('pop all patches')),
2761 2761 ('n', 'name', '',
2762 2762 _('queue name to pop (DEPRECATED)'), _('NAME')),
2763 2763 ('', 'keep-changes', None,
2764 2764 _('tolerate non-conflicting local changes')),
2765 2765 ('f', 'force', None, _('forget any local changes to patched files')),
2766 2766 ('', 'no-backup', None, _('do not save backup copies of files'))],
2767 2767 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2768 2768 def pop(ui, repo, patch=None, **opts):
2769 2769 """pop the current patch off the stack
2770 2770
2771 2771 Without argument, pops off the top of the patch stack. If given a
2772 2772 patch name, keeps popping off patches until the named patch is at
2773 2773 the top of the stack.
2774 2774
2775 2775 By default, abort if the working directory contains uncommitted
2776 2776 changes. With --keep-changes, abort only if the uncommitted files
2777 2777 overlap with patched files. With -f/--force, backup and discard
2778 2778 changes made to such files.
2779 2779
2780 2780 Return 0 on success.
2781 2781 """
2782 2782 opts = fixkeepchangesopts(ui, opts)
2783 2783 localupdate = True
2784 2784 if opts.get('name'):
2785 2785 q = queue(ui, repo.path, repo.join(opts.get('name')))
2786 2786 ui.warn(_('using patch queue: %s\n') % q.path)
2787 2787 localupdate = False
2788 2788 else:
2789 2789 q = repo.mq
2790 2790 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2791 2791 all=opts.get('all'), nobackup=opts.get('no_backup'),
2792 2792 keepchanges=opts.get('keep_changes'))
2793 2793 q.savedirty()
2794 2794 return ret
2795 2795
2796 2796 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2797 2797 def rename(ui, repo, patch, name=None, **opts):
2798 2798 """rename a patch
2799 2799
2800 2800 With one argument, renames the current patch to PATCH1.
2801 2801 With two arguments, renames PATCH1 to PATCH2.
2802 2802
2803 2803 Returns 0 on success."""
2804 2804 q = repo.mq
2805 2805 if not name:
2806 2806 name = patch
2807 2807 patch = None
2808 2808
2809 2809 if patch:
2810 2810 patch = q.lookup(patch)
2811 2811 else:
2812 2812 if not q.applied:
2813 2813 ui.write(_('no patches applied\n'))
2814 2814 return
2815 2815 patch = q.lookup('qtip')
2816 2816 absdest = q.join(name)
2817 2817 if os.path.isdir(absdest):
2818 2818 name = normname(os.path.join(name, os.path.basename(patch)))
2819 2819 absdest = q.join(name)
2820 2820 q.checkpatchname(name)
2821 2821
2822 2822 ui.note(_('renaming %s to %s\n') % (patch, name))
2823 2823 i = q.findseries(patch)
2824 2824 guards = q.guard_re.findall(q.fullseries[i])
2825 2825 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2826 2826 q.parseseries()
2827 2827 q.seriesdirty = True
2828 2828
2829 2829 info = q.isapplied(patch)
2830 2830 if info:
2831 2831 q.applied[info[0]] = statusentry(info[1], name)
2832 2832 q.applieddirty = True
2833 2833
2834 2834 destdir = os.path.dirname(absdest)
2835 2835 if not os.path.isdir(destdir):
2836 2836 os.makedirs(destdir)
2837 2837 util.rename(q.join(patch), absdest)
2838 2838 r = q.qrepo()
2839 2839 if r and patch in r.dirstate:
2840 2840 wctx = r[None]
2841 2841 wlock = r.wlock()
2842 2842 try:
2843 2843 if r.dirstate[patch] == 'a':
2844 2844 r.dirstate.drop(patch)
2845 2845 r.dirstate.add(name)
2846 2846 else:
2847 2847 wctx.copy(patch, name)
2848 2848 wctx.forget([patch])
2849 2849 finally:
2850 2850 wlock.release()
2851 2851
2852 2852 q.savedirty()
2853 2853
2854 2854 @command("qrestore",
2855 2855 [('d', 'delete', None, _('delete save entry')),
2856 2856 ('u', 'update', None, _('update queue working directory'))],
2857 2857 _('hg qrestore [-d] [-u] REV'))
2858 2858 def restore(ui, repo, rev, **opts):
2859 2859 """restore the queue state saved by a revision (DEPRECATED)
2860 2860
2861 2861 This command is deprecated, use :hg:`rebase` instead."""
2862 2862 rev = repo.lookup(rev)
2863 2863 q = repo.mq
2864 2864 q.restore(repo, rev, delete=opts.get('delete'),
2865 2865 qupdate=opts.get('update'))
2866 2866 q.savedirty()
2867 2867 return 0
2868 2868
2869 2869 @command("qsave",
2870 2870 [('c', 'copy', None, _('copy patch directory')),
2871 2871 ('n', 'name', '',
2872 2872 _('copy directory name'), _('NAME')),
2873 2873 ('e', 'empty', None, _('clear queue status file')),
2874 2874 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2875 2875 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2876 2876 def save(ui, repo, **opts):
2877 2877 """save current queue state (DEPRECATED)
2878 2878
2879 2879 This command is deprecated, use :hg:`rebase` instead."""
2880 2880 q = repo.mq
2881 2881 message = cmdutil.logmessage(ui, opts)
2882 2882 ret = q.save(repo, msg=message)
2883 2883 if ret:
2884 2884 return ret
2885 2885 q.savedirty() # save to .hg/patches before copying
2886 2886 if opts.get('copy'):
2887 2887 path = q.path
2888 2888 if opts.get('name'):
2889 2889 newpath = os.path.join(q.basepath, opts.get('name'))
2890 2890 if os.path.exists(newpath):
2891 2891 if not os.path.isdir(newpath):
2892 2892 raise util.Abort(_('destination %s exists and is not '
2893 2893 'a directory') % newpath)
2894 2894 if not opts.get('force'):
2895 2895 raise util.Abort(_('destination %s exists, '
2896 2896 'use -f to force') % newpath)
2897 2897 else:
2898 2898 newpath = savename(path)
2899 2899 ui.warn(_("copy %s to %s\n") % (path, newpath))
2900 2900 util.copyfiles(path, newpath)
2901 2901 if opts.get('empty'):
2902 2902 del q.applied[:]
2903 2903 q.applieddirty = True
2904 2904 q.savedirty()
2905 2905 return 0
2906 2906
2907 2907 @command("strip",
2908 2908 [
2909 2909 ('r', 'rev', [], _('strip specified revision (optional, '
2910 2910 'can specify revisions without this '
2911 2911 'option)'), _('REV')),
2912 2912 ('f', 'force', None, _('force removal of changesets, discard '
2913 2913 'uncommitted changes (no backup)')),
2914 2914 ('b', 'backup', None, _('bundle only changesets with local revision'
2915 2915 ' number greater than REV which are not'
2916 2916 ' descendants of REV (DEPRECATED)')),
2917 2917 ('', 'no-backup', None, _('no backups')),
2918 2918 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2919 2919 ('n', '', None, _('ignored (DEPRECATED)')),
2920 2920 ('k', 'keep', None, _("do not modify working copy during strip")),
2921 2921 ('B', 'bookmark', '', _("remove revs only reachable from given"
2922 2922 " bookmark"))],
2923 2923 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2924 2924 def strip(ui, repo, *revs, **opts):
2925 2925 """strip changesets and all their descendants from the repository
2926 2926
2927 2927 The strip command removes the specified changesets and all their
2928 2928 descendants. If the working directory has uncommitted changes, the
2929 2929 operation is aborted unless the --force flag is supplied, in which
2930 2930 case changes will be discarded.
2931 2931
2932 2932 If a parent of the working directory is stripped, then the working
2933 2933 directory will automatically be updated to the most recent
2934 2934 available ancestor of the stripped parent after the operation
2935 2935 completes.
2936 2936
2937 2937 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2938 2938 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2939 2939 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2940 2940 where BUNDLE is the bundle file created by the strip. Note that
2941 2941 the local revision numbers will in general be different after the
2942 2942 restore.
2943 2943
2944 2944 Use the --no-backup option to discard the backup bundle once the
2945 2945 operation completes.
2946 2946
2947 2947 Strip is not a history-rewriting operation and can be used on
2948 2948 changesets in the public phase. But if the stripped changesets have
2949 2949 been pushed to a remote repository you will likely pull them again.
2950 2950
2951 2951 Return 0 on success.
2952 2952 """
2953 2953 backup = 'all'
2954 2954 if opts.get('backup'):
2955 2955 backup = 'strip'
2956 2956 elif opts.get('no_backup') or opts.get('nobackup'):
2957 2957 backup = 'none'
2958 2958
2959 2959 cl = repo.changelog
2960 2960 revs = list(revs) + opts.get('rev')
2961 2961 revs = set(scmutil.revrange(repo, revs))
2962 2962
2963 2963 if opts.get('bookmark'):
2964 2964 mark = opts.get('bookmark')
2965 2965 marks = repo._bookmarks
2966 2966 if mark not in marks:
2967 2967 raise util.Abort(_("bookmark '%s' not found") % mark)
2968 2968
2969 2969 # If the requested bookmark is not the only one pointing to a
2970 2970 # a revision we have to only delete the bookmark and not strip
2971 2971 # anything. revsets cannot detect that case.
2972 2972 uniquebm = True
2973 2973 for m, n in marks.iteritems():
2974 2974 if m != mark and n == repo[mark].node():
2975 2975 uniquebm = False
2976 2976 break
2977 2977 if uniquebm:
2978 2978 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2979 2979 "ancestors(head() and not bookmark(%s)) - "
2980 2980 "ancestors(bookmark() and not bookmark(%s))",
2981 2981 mark, mark, mark)
2982 2982 revs.update(set(rsrevs))
2983 2983 if not revs:
2984 2984 del marks[mark]
2985 2985 repo._writebookmarks(mark)
2986 2986 ui.write(_("bookmark '%s' deleted\n") % mark)
2987 2987
2988 2988 if not revs:
2989 2989 raise util.Abort(_('empty revision set'))
2990 2990
2991 2991 descendants = set(cl.descendants(revs))
2992 2992 strippedrevs = revs.union(descendants)
2993 2993 roots = revs.difference(descendants)
2994 2994
2995 2995 update = False
2996 2996 # if one of the wdir parent is stripped we'll need
2997 2997 # to update away to an earlier revision
2998 2998 for p in repo.dirstate.parents():
2999 2999 if p != nullid and cl.rev(p) in strippedrevs:
3000 3000 update = True
3001 3001 break
3002 3002
3003 3003 rootnodes = set(cl.node(r) for r in roots)
3004 3004
3005 3005 q = repo.mq
3006 3006 if q.applied:
3007 3007 # refresh queue state if we're about to strip
3008 3008 # applied patches
3009 3009 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3010 3010 q.applieddirty = True
3011 3011 start = 0
3012 3012 end = len(q.applied)
3013 3013 for i, statusentry in enumerate(q.applied):
3014 3014 if statusentry.node in rootnodes:
3015 3015 # if one of the stripped roots is an applied
3016 3016 # patch, only part of the queue is stripped
3017 3017 start = i
3018 3018 break
3019 3019 del q.applied[start:end]
3020 3020 q.savedirty()
3021 3021
3022 3022 revs = list(rootnodes)
3023 3023 if update and opts.get('keep'):
3024 3024 wlock = repo.wlock()
3025 3025 try:
3026 3026 urev = repo.mq.qparents(repo, revs[0])
3027 3027 repo.dirstate.rebuild(urev, repo[urev].manifest())
3028 3028 repo.dirstate.write()
3029 3029 update = False
3030 3030 finally:
3031 3031 wlock.release()
3032 3032
3033 3033 if opts.get('bookmark'):
3034 3034 del marks[mark]
3035 3035 repo._writebookmarks(marks)
3036 3036 ui.write(_("bookmark '%s' deleted\n") % mark)
3037 3037
3038 3038 repo.mq.strip(repo, revs, backup=backup, update=update,
3039 3039 force=opts.get('force'))
3040 3040
3041 3041 return 0
3042 3042
3043 3043 @command("qselect",
3044 3044 [('n', 'none', None, _('disable all guards')),
3045 3045 ('s', 'series', None, _('list all guards in series file')),
3046 3046 ('', 'pop', None, _('pop to before first guarded applied patch')),
3047 3047 ('', 'reapply', None, _('pop, then reapply patches'))],
3048 3048 _('hg qselect [OPTION]... [GUARD]...'))
3049 3049 def select(ui, repo, *args, **opts):
3050 3050 '''set or print guarded patches to push
3051 3051
3052 3052 Use the :hg:`qguard` command to set or print guards on patch, then use
3053 3053 qselect to tell mq which guards to use. A patch will be pushed if
3054 3054 it has no guards or any positive guards match the currently
3055 3055 selected guard, but will not be pushed if any negative guards
3056 3056 match the current guard. For example::
3057 3057
3058 3058 qguard foo.patch -- -stable (negative guard)
3059 3059 qguard bar.patch +stable (positive guard)
3060 3060 qselect stable
3061 3061
3062 3062 This activates the "stable" guard. mq will skip foo.patch (because
3063 3063 it has a negative match) but push bar.patch (because it has a
3064 3064 positive match).
3065 3065
3066 3066 With no arguments, prints the currently active guards.
3067 3067 With one argument, sets the active guard.
3068 3068
3069 3069 Use -n/--none to deactivate guards (no other arguments needed).
3070 3070 When no guards are active, patches with positive guards are
3071 3071 skipped and patches with negative guards are pushed.
3072 3072
3073 3073 qselect can change the guards on applied patches. It does not pop
3074 3074 guarded patches by default. Use --pop to pop back to the last
3075 3075 applied patch that is not guarded. Use --reapply (which implies
3076 3076 --pop) to push back to the current patch afterwards, but skip
3077 3077 guarded patches.
3078 3078
3079 3079 Use -s/--series to print a list of all guards in the series file
3080 3080 (no other arguments needed). Use -v for more information.
3081 3081
3082 3082 Returns 0 on success.'''
3083 3083
3084 3084 q = repo.mq
3085 3085 guards = q.active()
3086 3086 if args or opts.get('none'):
3087 3087 old_unapplied = q.unapplied(repo)
3088 3088 old_guarded = [i for i in xrange(len(q.applied)) if
3089 3089 not q.pushable(i)[0]]
3090 3090 q.setactive(args)
3091 3091 q.savedirty()
3092 3092 if not args:
3093 3093 ui.status(_('guards deactivated\n'))
3094 3094 if not opts.get('pop') and not opts.get('reapply'):
3095 3095 unapplied = q.unapplied(repo)
3096 3096 guarded = [i for i in xrange(len(q.applied))
3097 3097 if not q.pushable(i)[0]]
3098 3098 if len(unapplied) != len(old_unapplied):
3099 3099 ui.status(_('number of unguarded, unapplied patches has '
3100 3100 'changed from %d to %d\n') %
3101 3101 (len(old_unapplied), len(unapplied)))
3102 3102 if len(guarded) != len(old_guarded):
3103 3103 ui.status(_('number of guarded, applied patches has changed '
3104 3104 'from %d to %d\n') %
3105 3105 (len(old_guarded), len(guarded)))
3106 3106 elif opts.get('series'):
3107 3107 guards = {}
3108 3108 noguards = 0
3109 3109 for gs in q.seriesguards:
3110 3110 if not gs:
3111 3111 noguards += 1
3112 3112 for g in gs:
3113 3113 guards.setdefault(g, 0)
3114 3114 guards[g] += 1
3115 3115 if ui.verbose:
3116 3116 guards['NONE'] = noguards
3117 3117 guards = guards.items()
3118 3118 guards.sort(key=lambda x: x[0][1:])
3119 3119 if guards:
3120 3120 ui.note(_('guards in series file:\n'))
3121 3121 for guard, count in guards:
3122 3122 ui.note('%2d ' % count)
3123 3123 ui.write(guard, '\n')
3124 3124 else:
3125 3125 ui.note(_('no guards in series file\n'))
3126 3126 else:
3127 3127 if guards:
3128 3128 ui.note(_('active guards:\n'))
3129 3129 for g in guards:
3130 3130 ui.write(g, '\n')
3131 3131 else:
3132 3132 ui.write(_('no active guards\n'))
3133 3133 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3134 3134 popped = False
3135 3135 if opts.get('pop') or opts.get('reapply'):
3136 3136 for i in xrange(len(q.applied)):
3137 3137 pushable, reason = q.pushable(i)
3138 3138 if not pushable:
3139 3139 ui.status(_('popping guarded patches\n'))
3140 3140 popped = True
3141 3141 if i == 0:
3142 3142 q.pop(repo, all=True)
3143 3143 else:
3144 3144 q.pop(repo, str(i - 1))
3145 3145 break
3146 3146 if popped:
3147 3147 try:
3148 3148 if reapply:
3149 3149 ui.status(_('reapplying unguarded patches\n'))
3150 3150 q.push(repo, reapply)
3151 3151 finally:
3152 3152 q.savedirty()
3153 3153
3154 3154 @command("qfinish",
3155 3155 [('a', 'applied', None, _('finish all applied changesets'))],
3156 3156 _('hg qfinish [-a] [REV]...'))
3157 3157 def finish(ui, repo, *revrange, **opts):
3158 3158 """move applied patches into repository history
3159 3159
3160 3160 Finishes the specified revisions (corresponding to applied
3161 3161 patches) by moving them out of mq control into regular repository
3162 3162 history.
3163 3163
3164 3164 Accepts a revision range or the -a/--applied option. If --applied
3165 3165 is specified, all applied mq revisions are removed from mq
3166 3166 control. Otherwise, the given revisions must be at the base of the
3167 3167 stack of applied patches.
3168 3168
3169 3169 This can be especially useful if your changes have been applied to
3170 3170 an upstream repository, or if you are about to push your changes
3171 3171 to upstream.
3172 3172
3173 3173 Returns 0 on success.
3174 3174 """
3175 3175 if not opts.get('applied') and not revrange:
3176 3176 raise util.Abort(_('no revisions specified'))
3177 3177 elif opts.get('applied'):
3178 3178 revrange = ('qbase::qtip',) + revrange
3179 3179
3180 3180 q = repo.mq
3181 3181 if not q.applied:
3182 3182 ui.status(_('no patches applied\n'))
3183 3183 return 0
3184 3184
3185 3185 revs = scmutil.revrange(repo, revrange)
3186 3186 if repo['.'].rev() in revs and repo[None].files():
3187 3187 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3188 3188 # queue.finish may changes phases but leave the responsibility to lock the
3189 3189 # repo to the caller to avoid deadlock with wlock. This command code is
3190 3190 # responsibility for this locking.
3191 3191 lock = repo.lock()
3192 3192 try:
3193 3193 q.finish(repo, revs)
3194 3194 q.savedirty()
3195 3195 finally:
3196 3196 lock.release()
3197 3197 return 0
3198 3198
3199 3199 @command("qqueue",
3200 3200 [('l', 'list', False, _('list all available queues')),
3201 3201 ('', 'active', False, _('print name of active queue')),
3202 3202 ('c', 'create', False, _('create new queue')),
3203 3203 ('', 'rename', False, _('rename active queue')),
3204 3204 ('', 'delete', False, _('delete reference to queue')),
3205 3205 ('', 'purge', False, _('delete queue, and remove patch dir')),
3206 3206 ],
3207 3207 _('[OPTION] [QUEUE]'))
3208 3208 def qqueue(ui, repo, name=None, **opts):
3209 3209 '''manage multiple patch queues
3210 3210
3211 3211 Supports switching between different patch queues, as well as creating
3212 3212 new patch queues and deleting existing ones.
3213 3213
3214 3214 Omitting a queue name or specifying -l/--list will show you the registered
3215 3215 queues - by default the "normal" patches queue is registered. The currently
3216 3216 active queue will be marked with "(active)". Specifying --active will print
3217 3217 only the name of the active queue.
3218 3218
3219 3219 To create a new queue, use -c/--create. The queue is automatically made
3220 3220 active, except in the case where there are applied patches from the
3221 3221 currently active queue in the repository. Then the queue will only be
3222 3222 created and switching will fail.
3223 3223
3224 3224 To delete an existing queue, use --delete. You cannot delete the currently
3225 3225 active queue.
3226 3226
3227 3227 Returns 0 on success.
3228 3228 '''
3229 3229 q = repo.mq
3230 3230 _defaultqueue = 'patches'
3231 3231 _allqueues = 'patches.queues'
3232 3232 _activequeue = 'patches.queue'
3233 3233
3234 3234 def _getcurrent():
3235 3235 cur = os.path.basename(q.path)
3236 3236 if cur.startswith('patches-'):
3237 3237 cur = cur[8:]
3238 3238 return cur
3239 3239
3240 3240 def _noqueues():
3241 3241 try:
3242 3242 fh = repo.opener(_allqueues, 'r')
3243 3243 fh.close()
3244 3244 except IOError:
3245 3245 return True
3246 3246
3247 3247 return False
3248 3248
3249 3249 def _getqueues():
3250 3250 current = _getcurrent()
3251 3251
3252 3252 try:
3253 3253 fh = repo.opener(_allqueues, 'r')
3254 3254 queues = [queue.strip() for queue in fh if queue.strip()]
3255 3255 fh.close()
3256 3256 if current not in queues:
3257 3257 queues.append(current)
3258 3258 except IOError:
3259 3259 queues = [_defaultqueue]
3260 3260
3261 3261 return sorted(queues)
3262 3262
3263 3263 def _setactive(name):
3264 3264 if q.applied:
3265 3265 raise util.Abort(_('patches applied - cannot set new queue active'))
3266 3266 _setactivenocheck(name)
3267 3267
3268 3268 def _setactivenocheck(name):
3269 3269 fh = repo.opener(_activequeue, 'w')
3270 3270 if name != 'patches':
3271 3271 fh.write(name)
3272 3272 fh.close()
3273 3273
3274 3274 def _addqueue(name):
3275 3275 fh = repo.opener(_allqueues, 'a')
3276 3276 fh.write('%s\n' % (name,))
3277 3277 fh.close()
3278 3278
3279 3279 def _queuedir(name):
3280 3280 if name == 'patches':
3281 3281 return repo.join('patches')
3282 3282 else:
3283 3283 return repo.join('patches-' + name)
3284 3284
3285 3285 def _validname(name):
3286 3286 for n in name:
3287 3287 if n in ':\\/.':
3288 3288 return False
3289 3289 return True
3290 3290
3291 3291 def _delete(name):
3292 3292 if name not in existing:
3293 3293 raise util.Abort(_('cannot delete queue that does not exist'))
3294 3294
3295 3295 current = _getcurrent()
3296 3296
3297 3297 if name == current:
3298 3298 raise util.Abort(_('cannot delete currently active queue'))
3299 3299
3300 3300 fh = repo.opener('patches.queues.new', 'w')
3301 3301 for queue in existing:
3302 3302 if queue == name:
3303 3303 continue
3304 3304 fh.write('%s\n' % (queue,))
3305 3305 fh.close()
3306 3306 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3307 3307
3308 3308 if not name or opts.get('list') or opts.get('active'):
3309 3309 current = _getcurrent()
3310 3310 if opts.get('active'):
3311 3311 ui.write('%s\n' % (current,))
3312 3312 return
3313 3313 for queue in _getqueues():
3314 3314 ui.write('%s' % (queue,))
3315 3315 if queue == current and not ui.quiet:
3316 3316 ui.write(_(' (active)\n'))
3317 3317 else:
3318 3318 ui.write('\n')
3319 3319 return
3320 3320
3321 3321 if not _validname(name):
3322 3322 raise util.Abort(
3323 3323 _('invalid queue name, may not contain the characters ":\\/."'))
3324 3324
3325 3325 existing = _getqueues()
3326 3326
3327 3327 if opts.get('create'):
3328 3328 if name in existing:
3329 3329 raise util.Abort(_('queue "%s" already exists') % name)
3330 3330 if _noqueues():
3331 3331 _addqueue(_defaultqueue)
3332 3332 _addqueue(name)
3333 3333 _setactive(name)
3334 3334 elif opts.get('rename'):
3335 3335 current = _getcurrent()
3336 3336 if name == current:
3337 3337 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3338 3338 if name in existing:
3339 3339 raise util.Abort(_('queue "%s" already exists') % name)
3340 3340
3341 3341 olddir = _queuedir(current)
3342 3342 newdir = _queuedir(name)
3343 3343
3344 3344 if os.path.exists(newdir):
3345 3345 raise util.Abort(_('non-queue directory "%s" already exists') %
3346 3346 newdir)
3347 3347
3348 3348 fh = repo.opener('patches.queues.new', 'w')
3349 3349 for queue in existing:
3350 3350 if queue == current:
3351 3351 fh.write('%s\n' % (name,))
3352 3352 if os.path.exists(olddir):
3353 3353 util.rename(olddir, newdir)
3354 3354 else:
3355 3355 fh.write('%s\n' % (queue,))
3356 3356 fh.close()
3357 3357 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3358 3358 _setactivenocheck(name)
3359 3359 elif opts.get('delete'):
3360 3360 _delete(name)
3361 3361 elif opts.get('purge'):
3362 3362 if name in existing:
3363 3363 _delete(name)
3364 3364 qdir = _queuedir(name)
3365 3365 if os.path.exists(qdir):
3366 3366 shutil.rmtree(qdir)
3367 3367 else:
3368 3368 if name not in existing:
3369 3369 raise util.Abort(_('use --create to create a new queue'))
3370 3370 _setactive(name)
3371 3371
3372 3372 def mqphasedefaults(repo, roots):
3373 3373 """callback used to set mq changeset as secret when no phase data exists"""
3374 3374 if repo.mq.applied:
3375 3375 if repo.ui.configbool('mq', 'secret', False):
3376 3376 mqphase = phases.secret
3377 3377 else:
3378 3378 mqphase = phases.draft
3379 3379 qbase = repo[repo.mq.applied[0].node]
3380 3380 roots[mqphase].add(qbase.node())
3381 3381 return roots
3382 3382
3383 3383 def reposetup(ui, repo):
3384 3384 class mqrepo(repo.__class__):
3385 3385 @util.propertycache
3386 3386 def mq(self):
3387 3387 return queue(self.ui, self.path)
3388 3388
3389 3389 def abortifwdirpatched(self, errmsg, force=False):
3390 3390 if self.mq.applied and not force:
3391 3391 parents = self.dirstate.parents()
3392 3392 patches = [s.node for s in self.mq.applied]
3393 3393 if parents[0] in patches or parents[1] in patches:
3394 3394 raise util.Abort(errmsg)
3395 3395
3396 3396 def commit(self, text="", user=None, date=None, match=None,
3397 3397 force=False, editor=False, extra={}):
3398 3398 self.abortifwdirpatched(
3399 3399 _('cannot commit over an applied mq patch'),
3400 3400 force)
3401 3401
3402 3402 return super(mqrepo, self).commit(text, user, date, match, force,
3403 3403 editor, extra)
3404 3404
3405 3405 def checkpush(self, force, revs):
3406 3406 if self.mq.applied and not force:
3407 3407 outapplied = [e.node for e in self.mq.applied]
3408 3408 if revs:
3409 3409 # Assume applied patches have no non-patch descendants and
3410 3410 # are not on remote already. Filtering any changeset not
3411 3411 # pushed.
3412 3412 heads = set(revs)
3413 3413 for node in reversed(outapplied):
3414 3414 if node in heads:
3415 3415 break
3416 3416 else:
3417 3417 outapplied.pop()
3418 3418 # looking for pushed and shared changeset
3419 3419 for node in outapplied:
3420 3420 if repo[node].phase() < phases.secret:
3421 3421 raise util.Abort(_('source has mq patches applied'))
3422 3422 # no non-secret patches pushed
3423 3423 super(mqrepo, self).checkpush(force, revs)
3424 3424
3425 3425 def _findtags(self):
3426 3426 '''augment tags from base class with patch tags'''
3427 3427 result = super(mqrepo, self)._findtags()
3428 3428
3429 3429 q = self.mq
3430 3430 if not q.applied:
3431 3431 return result
3432 3432
3433 3433 mqtags = [(patch.node, patch.name) for patch in q.applied]
3434 3434
3435 3435 try:
3436 3436 self.changelog.rev(mqtags[-1][0])
3437 3437 except error.LookupError:
3438 3438 self.ui.warn(_('mq status file refers to unknown node %s\n')
3439 3439 % short(mqtags[-1][0]))
3440 3440 return result
3441 3441
3442 3442 mqtags.append((mqtags[-1][0], 'qtip'))
3443 3443 mqtags.append((mqtags[0][0], 'qbase'))
3444 3444 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3445 3445 tags = result[0]
3446 3446 for patch in mqtags:
3447 3447 if patch[1] in tags:
3448 3448 self.ui.warn(_('tag %s overrides mq patch of the same '
3449 3449 'name\n') % patch[1])
3450 3450 else:
3451 3451 tags[patch[1]] = patch[0]
3452 3452
3453 3453 return result
3454 3454
3455 3455 def _branchtags(self, partial, lrev):
3456 3456 q = self.mq
3457 3457 cl = self.changelog
3458 3458 qbase = None
3459 3459 if not q.applied:
3460 3460 if getattr(self, '_committingpatch', False):
3461 3461 # Committing a new patch, must be tip
3462 3462 qbase = len(cl) - 1
3463 3463 else:
3464 3464 qbasenode = q.applied[0].node
3465 3465 try:
3466 3466 qbase = cl.rev(qbasenode)
3467 3467 except error.LookupError:
3468 3468 self.ui.warn(_('mq status file refers to unknown node %s\n')
3469 3469 % short(qbasenode))
3470 3470 if qbase is None:
3471 3471 return super(mqrepo, self)._branchtags(partial, lrev)
3472 3472
3473 3473 start = lrev + 1
3474 3474 if start < qbase:
3475 3475 # update the cache (excluding the patches) and save it
3476 3476 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3477 3477 self._updatebranchcache(partial, ctxgen)
3478 3478 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3479 3479 start = qbase
3480 3480 # if start = qbase, the cache is as updated as it should be.
3481 3481 # if start > qbase, the cache includes (part of) the patches.
3482 3482 # we might as well use it, but we won't save it.
3483 3483
3484 3484 # update the cache up to the tip
3485 3485 ctxgen = (self[r] for r in xrange(start, len(cl)))
3486 3486 self._updatebranchcache(partial, ctxgen)
3487 3487
3488 3488 return partial
3489 3489
3490 3490 if repo.local():
3491 3491 repo.__class__ = mqrepo
3492 3492
3493 3493 repo._phasedefaults.append(mqphasedefaults)
3494 3494
3495 3495 def mqimport(orig, ui, repo, *args, **kwargs):
3496 3496 if (util.safehasattr(repo, 'abortifwdirpatched')
3497 3497 and not kwargs.get('no_commit', False)):
3498 3498 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3499 3499 kwargs.get('force'))
3500 3500 return orig(ui, repo, *args, **kwargs)
3501 3501
3502 3502 def mqinit(orig, ui, *args, **kwargs):
3503 3503 mq = kwargs.pop('mq', None)
3504 3504
3505 3505 if not mq:
3506 3506 return orig(ui, *args, **kwargs)
3507 3507
3508 3508 if args:
3509 3509 repopath = args[0]
3510 3510 if not hg.islocal(repopath):
3511 3511 raise util.Abort(_('only a local queue repository '
3512 3512 'may be initialized'))
3513 3513 else:
3514 3514 repopath = cmdutil.findrepo(os.getcwd())
3515 3515 if not repopath:
3516 3516 raise util.Abort(_('there is no Mercurial repository here '
3517 3517 '(.hg not found)'))
3518 3518 repo = hg.repository(ui, repopath)
3519 3519 return qinit(ui, repo, True)
3520 3520
3521 3521 def mqcommand(orig, ui, repo, *args, **kwargs):
3522 3522 """Add --mq option to operate on patch repository instead of main"""
3523 3523
3524 3524 # some commands do not like getting unknown options
3525 3525 mq = kwargs.pop('mq', None)
3526 3526
3527 3527 if not mq:
3528 3528 return orig(ui, repo, *args, **kwargs)
3529 3529
3530 3530 q = repo.mq
3531 3531 r = q.qrepo()
3532 3532 if not r:
3533 3533 raise util.Abort(_('no queue repository'))
3534 3534 return orig(r.ui, r, *args, **kwargs)
3535 3535
3536 3536 def summary(orig, ui, repo, *args, **kwargs):
3537 3537 r = orig(ui, repo, *args, **kwargs)
3538 3538 q = repo.mq
3539 3539 m = []
3540 3540 a, u = len(q.applied), len(q.unapplied(repo))
3541 3541 if a:
3542 3542 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3543 3543 if u:
3544 3544 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3545 3545 if m:
3546 3546 ui.write("mq: %s\n" % ', '.join(m))
3547 3547 else:
3548 3548 ui.note(_("mq: (empty queue)\n"))
3549 3549 return r
3550 3550
3551 3551 def revsetmq(repo, subset, x):
3552 3552 """``mq()``
3553 3553 Changesets managed by MQ.
3554 3554 """
3555 3555 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3556 3556 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3557 3557 return [r for r in subset if r in applied]
3558 3558
3559 3559 # tell hggettext to extract docstrings from these functions:
3560 3560 i18nfunctions = [revsetmq]
3561 3561
3562 3562 def extsetup(ui):
3563 3563 # Ensure mq wrappers are called first, regardless of extension load order by
3564 3564 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3565 3565 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3566 3566
3567 3567 extensions.wrapcommand(commands.table, 'import', mqimport)
3568 3568 extensions.wrapcommand(commands.table, 'summary', summary)
3569 3569
3570 3570 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3571 3571 entry[1].extend(mqopt)
3572 3572
3573 3573 nowrap = set(commands.norepo.split(" "))
3574 3574
3575 3575 def dotable(cmdtable):
3576 3576 for cmd in cmdtable.keys():
3577 3577 cmd = cmdutil.parsealiases(cmd)[0]
3578 3578 if cmd in nowrap:
3579 3579 continue
3580 3580 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3581 3581 entry[1].extend(mqopt)
3582 3582
3583 3583 dotable(commands.table)
3584 3584
3585 3585 for extname, extmodule in extensions.extensions():
3586 3586 if extmodule.__file__ != __file__:
3587 3587 dotable(getattr(extmodule, 'cmdtable', {}))
3588 3588
3589 3589 revset.symbols['mq'] = revsetmq
3590 3590
3591 3591 colortable = {'qguard.negative': 'red',
3592 3592 'qguard.positive': 'yellow',
3593 3593 'qguard.unguarded': 'green',
3594 3594 'qseries.applied': 'blue bold underline',
3595 3595 'qseries.guarded': 'black bold',
3596 3596 'qseries.missing': 'red bold',
3597 3597 'qseries.unapplied': 'black bold'}
@@ -1,666 +1,666
1 1 # record.py
2 2 #
3 3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.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 '''commands to interactively select changes for commit/qrefresh'''
9 9
10 10 from mercurial.i18n import gettext, _
11 11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 12 from mercurial import util
13 13 import copy, cStringIO, errno, os, re, shutil, tempfile
14 14
15 15 cmdtable = {}
16 16 command = cmdutil.command(cmdtable)
17 17 testedwith = 'internal'
18 18
19 19 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
20 20
21 21 diffopts = [
22 22 ('w', 'ignore-all-space', False,
23 23 _('ignore white space when comparing lines')),
24 24 ('b', 'ignore-space-change', None,
25 25 _('ignore changes in the amount of white space')),
26 26 ('B', 'ignore-blank-lines', None,
27 27 _('ignore changes whose lines are all blank')),
28 28 ]
29 29
30 30 def scanpatch(fp):
31 31 """like patch.iterhunks, but yield different events
32 32
33 33 - ('file', [header_lines + fromfile + tofile])
34 34 - ('context', [context_lines])
35 35 - ('hunk', [hunk_lines])
36 - ('range', (-start,len, +start,len, diffp))
36 - ('range', (-start,len, +start,len, proc))
37 37 """
38 38 lr = patch.linereader(fp)
39 39
40 40 def scanwhile(first, p):
41 41 """scan lr while predicate holds"""
42 42 lines = [first]
43 43 while True:
44 44 line = lr.readline()
45 45 if not line:
46 46 break
47 47 if p(line):
48 48 lines.append(line)
49 49 else:
50 50 lr.push(line)
51 51 break
52 52 return lines
53 53
54 54 while True:
55 55 line = lr.readline()
56 56 if not line:
57 57 break
58 58 if line.startswith('diff --git a/') or line.startswith('diff -r '):
59 59 def notheader(line):
60 60 s = line.split(None, 1)
61 61 return not s or s[0] not in ('---', 'diff')
62 62 header = scanwhile(line, notheader)
63 63 fromfile = lr.readline()
64 64 if fromfile.startswith('---'):
65 65 tofile = lr.readline()
66 66 header += [fromfile, tofile]
67 67 else:
68 68 lr.push(fromfile)
69 69 yield 'file', header
70 70 elif line[0] == ' ':
71 71 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
72 72 elif line[0] in '-+':
73 73 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
74 74 else:
75 75 m = lines_re.match(line)
76 76 if m:
77 77 yield 'range', m.groups()
78 78 else:
79 79 raise patch.PatchError('unknown patch content: %r' % line)
80 80
81 81 class header(object):
82 82 """patch header
83 83
84 84 XXX shouldn't we move this to mercurial/patch.py ?
85 85 """
86 86 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
87 87 diff_re = re.compile('diff -r .* (.*)$')
88 88 allhunks_re = re.compile('(?:index|new file|deleted file) ')
89 89 pretty_re = re.compile('(?:new file|deleted file) ')
90 90 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
91 91
92 92 def __init__(self, header):
93 93 self.header = header
94 94 self.hunks = []
95 95
96 96 def binary(self):
97 97 return util.any(h.startswith('index ') for h in self.header)
98 98
99 99 def pretty(self, fp):
100 100 for h in self.header:
101 101 if h.startswith('index '):
102 102 fp.write(_('this modifies a binary file (all or nothing)\n'))
103 103 break
104 104 if self.pretty_re.match(h):
105 105 fp.write(h)
106 106 if self.binary():
107 107 fp.write(_('this is a binary file\n'))
108 108 break
109 109 if h.startswith('---'):
110 110 fp.write(_('%d hunks, %d lines changed\n') %
111 111 (len(self.hunks),
112 112 sum([max(h.added, h.removed) for h in self.hunks])))
113 113 break
114 114 fp.write(h)
115 115
116 116 def write(self, fp):
117 117 fp.write(''.join(self.header))
118 118
119 119 def allhunks(self):
120 120 return util.any(self.allhunks_re.match(h) for h in self.header)
121 121
122 122 def files(self):
123 123 match = self.diffgit_re.match(self.header[0])
124 124 if match:
125 125 fromfile, tofile = match.groups()
126 126 if fromfile == tofile:
127 127 return [fromfile]
128 128 return [fromfile, tofile]
129 129 else:
130 130 return self.diff_re.match(self.header[0]).groups()
131 131
132 132 def filename(self):
133 133 return self.files()[-1]
134 134
135 135 def __repr__(self):
136 136 return '<header %s>' % (' '.join(map(repr, self.files())))
137 137
138 138 def special(self):
139 139 return util.any(self.special_re.match(h) for h in self.header)
140 140
141 141 def countchanges(hunk):
142 142 """hunk -> (n+,n-)"""
143 143 add = len([h for h in hunk if h[0] == '+'])
144 144 rem = len([h for h in hunk if h[0] == '-'])
145 145 return add, rem
146 146
147 147 class hunk(object):
148 148 """patch hunk
149 149
150 150 XXX shouldn't we merge this with patch.hunk ?
151 151 """
152 152 maxcontext = 3
153 153
154 154 def __init__(self, header, fromline, toline, proc, before, hunk, after):
155 155 def trimcontext(number, lines):
156 156 delta = len(lines) - self.maxcontext
157 157 if False and delta > 0:
158 158 return number + delta, lines[:self.maxcontext]
159 159 return number, lines
160 160
161 161 self.header = header
162 162 self.fromline, self.before = trimcontext(fromline, before)
163 163 self.toline, self.after = trimcontext(toline, after)
164 164 self.proc = proc
165 165 self.hunk = hunk
166 166 self.added, self.removed = countchanges(self.hunk)
167 167
168 168 def write(self, fp):
169 169 delta = len(self.before) + len(self.after)
170 170 if self.after and self.after[-1] == '\\ No newline at end of file\n':
171 171 delta -= 1
172 172 fromlen = delta + self.removed
173 173 tolen = delta + self.added
174 174 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
175 175 (self.fromline, fromlen, self.toline, tolen,
176 176 self.proc and (' ' + self.proc)))
177 177 fp.write(''.join(self.before + self.hunk + self.after))
178 178
179 179 pretty = write
180 180
181 181 def filename(self):
182 182 return self.header.filename()
183 183
184 184 def __repr__(self):
185 185 return '<hunk %r@%d>' % (self.filename(), self.fromline)
186 186
187 187 def parsepatch(fp):
188 188 """patch -> [] of headers -> [] of hunks """
189 189 class parser(object):
190 190 """patch parsing state machine"""
191 191 def __init__(self):
192 192 self.fromline = 0
193 193 self.toline = 0
194 194 self.proc = ''
195 195 self.header = None
196 196 self.context = []
197 197 self.before = []
198 198 self.hunk = []
199 199 self.headers = []
200 200
201 201 def addrange(self, limits):
202 202 fromstart, fromend, tostart, toend, proc = limits
203 203 self.fromline = int(fromstart)
204 204 self.toline = int(tostart)
205 205 self.proc = proc
206 206
207 207 def addcontext(self, context):
208 208 if self.hunk:
209 209 h = hunk(self.header, self.fromline, self.toline, self.proc,
210 210 self.before, self.hunk, context)
211 211 self.header.hunks.append(h)
212 212 self.fromline += len(self.before) + h.removed
213 213 self.toline += len(self.before) + h.added
214 214 self.before = []
215 215 self.hunk = []
216 216 self.proc = ''
217 217 self.context = context
218 218
219 219 def addhunk(self, hunk):
220 220 if self.context:
221 221 self.before = self.context
222 222 self.context = []
223 223 self.hunk = hunk
224 224
225 225 def newfile(self, hdr):
226 226 self.addcontext([])
227 227 h = header(hdr)
228 228 self.headers.append(h)
229 229 self.header = h
230 230
231 231 def finished(self):
232 232 self.addcontext([])
233 233 return self.headers
234 234
235 235 transitions = {
236 236 'file': {'context': addcontext,
237 237 'file': newfile,
238 238 'hunk': addhunk,
239 239 'range': addrange},
240 240 'context': {'file': newfile,
241 241 'hunk': addhunk,
242 242 'range': addrange},
243 243 'hunk': {'context': addcontext,
244 244 'file': newfile,
245 245 'range': addrange},
246 246 'range': {'context': addcontext,
247 247 'hunk': addhunk},
248 248 }
249 249
250 250 p = parser()
251 251
252 252 state = 'context'
253 253 for newstate, data in scanpatch(fp):
254 254 try:
255 255 p.transitions[state][newstate](p, data)
256 256 except KeyError:
257 257 raise patch.PatchError('unhandled transition: %s -> %s' %
258 258 (state, newstate))
259 259 state = newstate
260 260 return p.finished()
261 261
262 262 def filterpatch(ui, headers):
263 263 """Interactively filter patch chunks into applied-only chunks"""
264 264
265 265 def prompt(skipfile, skipall, query, chunk):
266 266 """prompt query, and process base inputs
267 267
268 268 - y/n for the rest of file
269 269 - y/n for the rest
270 270 - ? (help)
271 271 - q (quit)
272 272
273 273 Return True/False and possibly updated skipfile and skipall.
274 274 """
275 275 newpatches = None
276 276 if skipall is not None:
277 277 return skipall, skipfile, skipall, newpatches
278 278 if skipfile is not None:
279 279 return skipfile, skipfile, skipall, newpatches
280 280 while True:
281 281 resps = _('[Ynesfdaq?]')
282 282 choices = (_('&Yes, record this change'),
283 283 _('&No, skip this change'),
284 284 _('&Edit the change manually'),
285 285 _('&Skip remaining changes to this file'),
286 286 _('Record remaining changes to this &file'),
287 287 _('&Done, skip remaining changes and files'),
288 288 _('Record &all changes to all remaining files'),
289 289 _('&Quit, recording no changes'),
290 290 _('&?'))
291 291 r = ui.promptchoice("%s %s" % (query, resps), choices)
292 292 ui.write("\n")
293 293 if r == 8: # ?
294 294 doc = gettext(record.__doc__)
295 295 c = doc.find('::') + 2
296 296 for l in doc[c:].splitlines():
297 297 if l.startswith(' '):
298 298 ui.write(l.strip(), '\n')
299 299 continue
300 300 elif r == 0: # yes
301 301 ret = True
302 302 elif r == 1: # no
303 303 ret = False
304 304 elif r == 2: # Edit patch
305 305 if chunk is None:
306 306 ui.write(_('cannot edit patch for whole file'))
307 307 ui.write("\n")
308 308 continue
309 309 if chunk.header.binary():
310 310 ui.write(_('cannot edit patch for binary file'))
311 311 ui.write("\n")
312 312 continue
313 313 # Patch comment based on the Git one (based on comment at end of
314 314 # http://mercurial.selenic.com/wiki/RecordExtension)
315 315 phelp = '---' + _("""
316 316 To remove '-' lines, make them ' ' lines (context).
317 317 To remove '+' lines, delete them.
318 318 Lines starting with # will be removed from the patch.
319 319
320 320 If the patch applies cleanly, the edited hunk will immediately be
321 321 added to the record list. If it does not apply cleanly, a rejects
322 322 file will be generated: you can use that when you try again. If
323 323 all lines of the hunk are removed, then the edit is aborted and
324 324 the hunk is left unchanged.
325 325 """)
326 326 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
327 327 suffix=".diff", text=True)
328 328 ncpatchfp = None
329 329 try:
330 330 # Write the initial patch
331 331 f = os.fdopen(patchfd, "w")
332 332 chunk.header.write(f)
333 333 chunk.write(f)
334 334 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
335 335 f.close()
336 336 # Start the editor and wait for it to complete
337 337 editor = ui.geteditor()
338 338 util.system("%s \"%s\"" % (editor, patchfn),
339 339 environ={'HGUSER': ui.username()},
340 340 onerr=util.Abort, errprefix=_("edit failed"),
341 341 out=ui.fout)
342 342 # Remove comment lines
343 343 patchfp = open(patchfn)
344 344 ncpatchfp = cStringIO.StringIO()
345 345 for line in patchfp:
346 346 if not line.startswith('#'):
347 347 ncpatchfp.write(line)
348 348 patchfp.close()
349 349 ncpatchfp.seek(0)
350 350 newpatches = parsepatch(ncpatchfp)
351 351 finally:
352 352 os.unlink(patchfn)
353 353 del ncpatchfp
354 354 # Signal that the chunk shouldn't be applied as-is, but
355 355 # provide the new patch to be used instead.
356 356 ret = False
357 357 elif r == 3: # Skip
358 358 ret = skipfile = False
359 359 elif r == 4: # file (Record remaining)
360 360 ret = skipfile = True
361 361 elif r == 5: # done, skip remaining
362 362 ret = skipall = False
363 363 elif r == 6: # all
364 364 ret = skipall = True
365 365 elif r == 7: # quit
366 366 raise util.Abort(_('user quit'))
367 367 return ret, skipfile, skipall, newpatches
368 368
369 369 seen = set()
370 370 applied = {} # 'filename' -> [] of chunks
371 371 skipfile, skipall = None, None
372 372 pos, total = 1, sum(len(h.hunks) for h in headers)
373 373 for h in headers:
374 374 pos += len(h.hunks)
375 375 skipfile = None
376 376 fixoffset = 0
377 377 hdr = ''.join(h.header)
378 378 if hdr in seen:
379 379 continue
380 380 seen.add(hdr)
381 381 if skipall is None:
382 382 h.pretty(ui)
383 383 msg = (_('examine changes to %s?') %
384 384 _(' and ').join("'%s'" % f for f in h.files()))
385 385 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
386 386 if not r:
387 387 continue
388 388 applied[h.filename()] = [h]
389 389 if h.allhunks():
390 390 applied[h.filename()] += h.hunks
391 391 continue
392 392 for i, chunk in enumerate(h.hunks):
393 393 if skipfile is None and skipall is None:
394 394 chunk.pretty(ui)
395 395 if total == 1:
396 396 msg = _('record this change to %r?') % chunk.filename()
397 397 else:
398 398 idx = pos - len(h.hunks) + i
399 399 msg = _('record change %d/%d to %r?') % (idx, total,
400 400 chunk.filename())
401 401 r, skipfile, skipall, newpatches = prompt(skipfile,
402 402 skipall, msg, chunk)
403 403 if r:
404 404 if fixoffset:
405 405 chunk = copy.copy(chunk)
406 406 chunk.toline += fixoffset
407 407 applied[chunk.filename()].append(chunk)
408 408 elif newpatches is not None:
409 409 for newpatch in newpatches:
410 410 for newhunk in newpatch.hunks:
411 411 if fixoffset:
412 412 newhunk.toline += fixoffset
413 413 applied[newhunk.filename()].append(newhunk)
414 414 else:
415 415 fixoffset += chunk.removed - chunk.added
416 416 return sum([h for h in applied.itervalues()
417 417 if h[0].special() or len(h) > 1], [])
418 418
419 419 @command("record",
420 420 # same options as commit + white space diff options
421 421 commands.table['^commit|ci'][1][:] + diffopts,
422 422 _('hg record [OPTION]... [FILE]...'))
423 423 def record(ui, repo, *pats, **opts):
424 424 '''interactively select changes to commit
425 425
426 426 If a list of files is omitted, all changes reported by :hg:`status`
427 427 will be candidates for recording.
428 428
429 429 See :hg:`help dates` for a list of formats valid for -d/--date.
430 430
431 431 You will be prompted for whether to record changes to each
432 432 modified file, and for files with multiple changes, for each
433 433 change to use. For each query, the following responses are
434 434 possible::
435 435
436 436 y - record this change
437 437 n - skip this change
438 438 e - edit this change manually
439 439
440 440 s - skip remaining changes to this file
441 441 f - record remaining changes to this file
442 442
443 443 d - done, skip remaining changes and files
444 444 a - record all changes to all remaining files
445 445 q - quit, recording no changes
446 446
447 447 ? - display help
448 448
449 449 This command is not available when committing a merge.'''
450 450
451 451 dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
452 452
453 453 def qrefresh(origfn, ui, repo, *pats, **opts):
454 454 if not opts['interactive']:
455 455 return origfn(ui, repo, *pats, **opts)
456 456
457 457 mq = extensions.find('mq')
458 458
459 459 def committomq(ui, repo, *pats, **opts):
460 460 # At this point the working copy contains only changes that
461 461 # were accepted. All other changes were reverted.
462 462 # We can't pass *pats here since qrefresh will undo all other
463 463 # changed files in the patch that aren't in pats.
464 464 mq.refresh(ui, repo, **opts)
465 465
466 466 # backup all changed files
467 467 dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
468 468
469 469 def qrecord(ui, repo, patch, *pats, **opts):
470 470 '''interactively record a new patch
471 471
472 472 See :hg:`help qnew` & :hg:`help record` for more information and
473 473 usage.
474 474 '''
475 475
476 476 try:
477 477 mq = extensions.find('mq')
478 478 except KeyError:
479 479 raise util.Abort(_("'mq' extension not loaded"))
480 480
481 481 repo.mq.checkpatchname(patch)
482 482
483 483 def committomq(ui, repo, *pats, **opts):
484 484 opts['checkname'] = False
485 485 mq.new(ui, repo, patch, *pats, **opts)
486 486
487 487 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
488 488
489 489 def qnew(origfn, ui, repo, patch, *args, **opts):
490 490 if opts['interactive']:
491 491 return qrecord(ui, repo, patch, *args, **opts)
492 492 return origfn(ui, repo, patch, *args, **opts)
493 493
494 494 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
495 495 if not ui.interactive():
496 496 raise util.Abort(_('running non-interactively, use %s instead') %
497 497 cmdsuggest)
498 498
499 499 def recordfunc(ui, repo, message, match, opts):
500 500 """This is generic record driver.
501 501
502 502 Its job is to interactively filter local changes, and
503 503 accordingly prepare working directory into a state in which the
504 504 job can be delegated to a non-interactive commit command such as
505 505 'commit' or 'qrefresh'.
506 506
507 507 After the actual job is done by non-interactive command, the
508 508 working directory is restored to its original state.
509 509
510 510 In the end we'll record interesting changes, and everything else
511 511 will be left in place, so the user can continue working.
512 512 """
513 513
514 514 merge = len(repo[None].parents()) > 1
515 515 if merge:
516 516 raise util.Abort(_('cannot partially commit a merge '
517 517 '(use "hg commit" instead)'))
518 518
519 519 changes = repo.status(match=match)[:3]
520 520 diffopts = mdiff.diffopts(
521 521 git=True, nodates=True,
522 522 ignorews=opts.get('ignore_all_space'),
523 523 ignorewsamount=opts.get('ignore_space_change'),
524 524 ignoreblanklines=opts.get('ignore_blank_lines'))
525 525 chunks = patch.diff(repo, changes=changes, opts=diffopts)
526 526 fp = cStringIO.StringIO()
527 527 fp.write(''.join(chunks))
528 528 fp.seek(0)
529 529
530 530 # 1. filter patch, so we have intending-to apply subset of it
531 531 chunks = filterpatch(ui, parsepatch(fp))
532 532 del fp
533 533
534 534 contenders = set()
535 535 for h in chunks:
536 536 try:
537 537 contenders.update(set(h.files()))
538 538 except AttributeError:
539 539 pass
540 540
541 541 changed = changes[0] + changes[1] + changes[2]
542 542 newfiles = [f for f in changed if f in contenders]
543 543 if not newfiles:
544 544 ui.status(_('no changes to record\n'))
545 545 return 0
546 546
547 547 modified = set(changes[0])
548 548
549 549 # 2. backup changed files, so we can restore them in the end
550 550 if backupall:
551 551 tobackup = changed
552 552 else:
553 553 tobackup = [f for f in newfiles if f in modified]
554 554
555 555 backups = {}
556 556 if tobackup:
557 557 backupdir = repo.join('record-backups')
558 558 try:
559 559 os.mkdir(backupdir)
560 560 except OSError, err:
561 561 if err.errno != errno.EEXIST:
562 562 raise
563 563 try:
564 564 # backup continues
565 565 for f in tobackup:
566 566 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
567 567 dir=backupdir)
568 568 os.close(fd)
569 569 ui.debug('backup %r as %r\n' % (f, tmpname))
570 570 util.copyfile(repo.wjoin(f), tmpname)
571 571 shutil.copystat(repo.wjoin(f), tmpname)
572 572 backups[f] = tmpname
573 573
574 574 fp = cStringIO.StringIO()
575 575 for c in chunks:
576 576 if c.filename() in backups:
577 577 c.write(fp)
578 578 dopatch = fp.tell()
579 579 fp.seek(0)
580 580
581 581 # 3a. apply filtered patch to clean repo (clean)
582 582 if backups:
583 583 hg.revert(repo, repo.dirstate.p1(),
584 584 lambda key: key in backups)
585 585
586 586 # 3b. (apply)
587 587 if dopatch:
588 588 try:
589 589 ui.debug('applying patch\n')
590 590 ui.debug(fp.getvalue())
591 591 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
592 592 except patch.PatchError, err:
593 593 raise util.Abort(str(err))
594 594 del fp
595 595
596 596 # 4. We prepared working directory according to filtered
597 597 # patch. Now is the time to delegate the job to
598 598 # commit/qrefresh or the like!
599 599
600 600 # it is important to first chdir to repo root -- we'll call
601 601 # a highlevel command with list of pathnames relative to
602 602 # repo root
603 603 cwd = os.getcwd()
604 604 os.chdir(repo.root)
605 605 try:
606 606 commitfunc(ui, repo, *newfiles, **opts)
607 607 finally:
608 608 os.chdir(cwd)
609 609
610 610 return 0
611 611 finally:
612 612 # 5. finally restore backed-up files
613 613 try:
614 614 for realname, tmpname in backups.iteritems():
615 615 ui.debug('restoring %r to %r\n' % (tmpname, realname))
616 616 util.copyfile(tmpname, repo.wjoin(realname))
617 617 # Our calls to copystat() here and above are a
618 618 # hack to trick any editors that have f open that
619 619 # we haven't modified them.
620 620 #
621 621 # Also note that this racy as an editor could
622 622 # notice the file's mtime before we've finished
623 623 # writing it.
624 624 shutil.copystat(tmpname, repo.wjoin(realname))
625 625 os.unlink(tmpname)
626 626 if tobackup:
627 627 os.rmdir(backupdir)
628 628 except OSError:
629 629 pass
630 630
631 631 # wrap ui.write so diff output can be labeled/colorized
632 632 def wrapwrite(orig, *args, **kw):
633 633 label = kw.pop('label', '')
634 634 for chunk, l in patch.difflabel(lambda: args):
635 635 orig(chunk, label=label + l)
636 636 oldwrite = ui.write
637 637 extensions.wrapfunction(ui, 'write', wrapwrite)
638 638 try:
639 639 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
640 640 finally:
641 641 ui.write = oldwrite
642 642
643 643 cmdtable["qrecord"] = \
644 644 (qrecord, [], # placeholder until mq is available
645 645 _('hg qrecord [OPTION]... PATCH [FILE]...'))
646 646
647 647 def uisetup(ui):
648 648 try:
649 649 mq = extensions.find('mq')
650 650 except KeyError:
651 651 return
652 652
653 653 cmdtable["qrecord"] = \
654 654 (qrecord,
655 655 # same options as qnew, but copy them so we don't get
656 656 # -i/--interactive for qrecord and add white space diff options
657 657 mq.cmdtable['^qnew'][1][:] + diffopts,
658 658 _('hg qrecord [OPTION]... PATCH [FILE]...'))
659 659
660 660 _wrapcmd('qnew', mq.cmdtable, qnew, _("interactively record a new patch"))
661 661 _wrapcmd('qrefresh', mq.cmdtable, qrefresh,
662 662 _("interactively select changes to refresh"))
663 663
664 664 def _wrapcmd(cmd, table, wrapfn, msg):
665 665 entry = extensions.wrapcommand(table, cmd, wrapfn)
666 666 entry[1].append(('i', 'interactive', None, msg))
@@ -1,101 +1,101
1 1 # Copyright 2009, Alexander Solovyov <piranha@piranha.org.ua>
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 """extend schemes with shortcuts to repository swarms
7 7
8 8 This extension allows you to specify shortcuts for parent URLs with a
9 9 lot of repositories to act like a scheme, for example::
10 10
11 11 [schemes]
12 12 py = http://code.python.org/hg/
13 13
14 14 After that you can use it like::
15 15
16 16 hg clone py://trunk/
17 17
18 18 Additionally there is support for some more complex schemas, for
19 19 example used by Google Code::
20 20
21 21 [schemes]
22 22 gcode = http://{1}.googlecode.com/hg/
23 23
24 24 The syntax is taken from Mercurial templates, and you have unlimited
25 25 number of variables, starting with ``{1}`` and continuing with
26 26 ``{2}``, ``{3}`` and so on. This variables will receive parts of URL
27 27 supplied, split by ``/``. Anything not specified as ``{part}`` will be
28 28 just appended to an URL.
29 29
30 30 For convenience, the extension adds these schemes by default::
31 31
32 32 [schemes]
33 33 py = http://hg.python.org/
34 34 bb = https://bitbucket.org/
35 35 bb+ssh = ssh://hg@bitbucket.org/
36 36 gcode = https://{1}.googlecode.com/hg/
37 37 kiln = https://{1}.kilnhg.com/Repo/
38 38
39 39 You can override a predefined scheme by defining a new scheme with the
40 40 same name.
41 41 """
42 42
43 43 import os, re
44 44 from mercurial import extensions, hg, templater, util
45 45 from mercurial.i18n import _
46 46
47 47 testedwith = 'internal'
48 48
49 49
50 50 class ShortRepository(object):
51 51 def __init__(self, url, scheme, templater):
52 52 self.scheme = scheme
53 53 self.templater = templater
54 54 self.url = url
55 55 try:
56 56 self.parts = max(map(int, re.findall(r'\{(\d+)\}', self.url)))
57 57 except ValueError:
58 58 self.parts = 0
59 59
60 60 def __repr__(self):
61 61 return '<ShortRepository: %s>' % self.scheme
62 62
63 63 def instance(self, ui, url, create):
64 # Should this use urlmod.url(), or is manual parsing better?
64 # Should this use the util.url class, or is manual parsing better?
65 65 url = url.split('://', 1)[1]
66 66 parts = url.split('/', self.parts)
67 67 if len(parts) > self.parts:
68 68 tail = parts[-1]
69 69 parts = parts[:-1]
70 70 else:
71 71 tail = ''
72 72 context = dict((str(i + 1), v) for i, v in enumerate(parts))
73 73 url = ''.join(self.templater.process(self.url, context)) + tail
74 74 return hg._peerlookup(url).instance(ui, url, create)
75 75
76 76 def hasdriveletter(orig, path):
77 77 if path:
78 78 for scheme in schemes:
79 79 if path.startswith(scheme + ':'):
80 80 return False
81 81 return orig(path)
82 82
83 83 schemes = {
84 84 'py': 'http://hg.python.org/',
85 85 'bb': 'https://bitbucket.org/',
86 86 'bb+ssh': 'ssh://hg@bitbucket.org/',
87 87 'gcode': 'https://{1}.googlecode.com/hg/',
88 88 'kiln': 'https://{1}.kilnhg.com/Repo/'
89 89 }
90 90
91 91 def extsetup(ui):
92 92 schemes.update(dict(ui.configitems('schemes')))
93 93 t = templater.engine(lambda x: x)
94 94 for scheme, url in schemes.items():
95 95 if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha()
96 96 and os.path.exists('%s:\\' % scheme)):
97 97 raise util.Abort(_('custom scheme %s:// conflicts with drive '
98 98 'letter %s:\\\n') % (scheme, scheme.upper()))
99 99 hg.schemes[scheme] = ShortRepository(url, scheme, t)
100 100
101 101 extensions.wrapfunction(util, 'hasdriveletter', hasdriveletter)
@@ -1,1582 +1,1582
1 1 """ Multicast DNS Service Discovery for Python, v0.12
2 2 Copyright (C) 2003, Paul Scott-Murphy
3 3
4 4 This module provides a framework for the use of DNS Service Discovery
5 5 using IP multicast. It has been tested against the JRendezvous
6 6 implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
7 7 and against the mDNSResponder from Mac OS X 10.3.8.
8 8
9 9 This library is free software; you can redistribute it and/or
10 10 modify it under the terms of the GNU Lesser General Public
11 11 License as published by the Free Software Foundation; either
12 12 version 2.1 of the License, or (at your option) any later version.
13 13
14 14 This library is distributed in the hope that it will be useful,
15 15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 17 Lesser General Public License for more details.
18 18
19 19 You should have received a copy of the GNU Lesser General Public
20 20 License along with this library; if not, see
21 21 <http://www.gnu.org/licenses/>.
22 22
23 23 """
24 24
25 25 """0.12 update - allow selection of binding interface
26 26 typo fix - Thanks A. M. Kuchlingi
27 27 removed all use of word 'Rendezvous' - this is an API change"""
28 28
29 29 """0.11 update - correction to comments for addListener method
30 30 support for new record types seen from OS X
31 31 - IPv6 address
32 32 - hostinfo
33 33 ignore unknown DNS record types
34 34 fixes to name decoding
35 35 works alongside other processes using port 5353 (e.g. on Mac OS X)
36 36 tested against Mac OS X 10.3.2's mDNSResponder
37 37 corrections to removal of list entries for service browser"""
38 38
39 39 """0.10 update - Jonathon Paisley contributed these corrections:
40 40 always multicast replies, even when query is unicast
41 41 correct a pointer encoding problem
42 42 can now write records in any order
43 43 traceback shown on failure
44 44 better TXT record parsing
45 45 server is now separate from name
46 46 can cancel a service browser
47 47
48 48 modified some unit tests to accommodate these changes"""
49 49
50 50 """0.09 update - remove all records on service unregistration
51 51 fix DOS security problem with readName"""
52 52
53 53 """0.08 update - changed licensing to LGPL"""
54 54
55 55 """0.07 update - faster shutdown on engine
56 56 pointer encoding of outgoing names
57 57 ServiceBrowser now works
58 58 new unit tests"""
59 59
60 60 """0.06 update - small improvements with unit tests
61 61 added defined exception types
62 62 new style objects
63 63 fixed hostname/interface problem
64 64 fixed socket timeout problem
65 65 fixed addServiceListener() typo bug
66 66 using select() for socket reads
67 67 tested on Debian unstable with Python 2.2.2"""
68 68
69 69 """0.05 update - ensure case insensitivity on domain names
70 70 support for unicast DNS queries"""
71 71
72 72 """0.04 update - added some unit tests
73 73 added __ne__ adjuncts where required
74 74 ensure names end in '.local.'
75 75 timeout on receiving socket for clean shutdown"""
76 76
77 77 __author__ = "Paul Scott-Murphy"
78 78 __email__ = "paul at scott dash murphy dot com"
79 79 __version__ = "0.12"
80 80
81 81 import string
82 82 import time
83 83 import struct
84 84 import socket
85 85 import threading
86 86 import select
87 87 import traceback
88 88
89 89 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
90 90
91 91 # hook for threads
92 92
93 93 globals()['_GLOBAL_DONE'] = 0
94 94
95 95 # Some timing constants
96 96
97 97 _UNREGISTER_TIME = 125
98 98 _CHECK_TIME = 175
99 99 _REGISTER_TIME = 225
100 100 _LISTENER_TIME = 200
101 101 _BROWSER_TIME = 500
102 102
103 103 # Some DNS constants
104 104
105 105 _MDNS_ADDR = '224.0.0.251'
106 106 _MDNS_PORT = 5353;
107 107 _DNS_PORT = 53;
108 108 _DNS_TTL = 60 * 60; # one hour default TTL
109 109
110 110 _MAX_MSG_TYPICAL = 1460 # unused
111 111 _MAX_MSG_ABSOLUTE = 8972
112 112
113 113 _FLAGS_QR_MASK = 0x8000 # query response mask
114 114 _FLAGS_QR_QUERY = 0x0000 # query
115 115 _FLAGS_QR_RESPONSE = 0x8000 # response
116 116
117 _FLAGS_AA = 0x0400 # Authorative answer
117 _FLAGS_AA = 0x0400 # Authoritative answer
118 118 _FLAGS_TC = 0x0200 # Truncated
119 119 _FLAGS_RD = 0x0100 # Recursion desired
120 120 _FLAGS_RA = 0x8000 # Recursion available
121 121
122 122 _FLAGS_Z = 0x0040 # Zero
123 123 _FLAGS_AD = 0x0020 # Authentic data
124 124 _FLAGS_CD = 0x0010 # Checking disabled
125 125
126 126 _CLASS_IN = 1
127 127 _CLASS_CS = 2
128 128 _CLASS_CH = 3
129 129 _CLASS_HS = 4
130 130 _CLASS_NONE = 254
131 131 _CLASS_ANY = 255
132 132 _CLASS_MASK = 0x7FFF
133 133 _CLASS_UNIQUE = 0x8000
134 134
135 135 _TYPE_A = 1
136 136 _TYPE_NS = 2
137 137 _TYPE_MD = 3
138 138 _TYPE_MF = 4
139 139 _TYPE_CNAME = 5
140 140 _TYPE_SOA = 6
141 141 _TYPE_MB = 7
142 142 _TYPE_MG = 8
143 143 _TYPE_MR = 9
144 144 _TYPE_NULL = 10
145 145 _TYPE_WKS = 11
146 146 _TYPE_PTR = 12
147 147 _TYPE_HINFO = 13
148 148 _TYPE_MINFO = 14
149 149 _TYPE_MX = 15
150 150 _TYPE_TXT = 16
151 151 _TYPE_AAAA = 28
152 152 _TYPE_SRV = 33
153 153 _TYPE_ANY = 255
154 154
155 155 # Mapping constants to names
156 156
157 157 _CLASSES = { _CLASS_IN : "in",
158 158 _CLASS_CS : "cs",
159 159 _CLASS_CH : "ch",
160 160 _CLASS_HS : "hs",
161 161 _CLASS_NONE : "none",
162 162 _CLASS_ANY : "any" }
163 163
164 164 _TYPES = { _TYPE_A : "a",
165 165 _TYPE_NS : "ns",
166 166 _TYPE_MD : "md",
167 167 _TYPE_MF : "mf",
168 168 _TYPE_CNAME : "cname",
169 169 _TYPE_SOA : "soa",
170 170 _TYPE_MB : "mb",
171 171 _TYPE_MG : "mg",
172 172 _TYPE_MR : "mr",
173 173 _TYPE_NULL : "null",
174 174 _TYPE_WKS : "wks",
175 175 _TYPE_PTR : "ptr",
176 176 _TYPE_HINFO : "hinfo",
177 177 _TYPE_MINFO : "minfo",
178 178 _TYPE_MX : "mx",
179 179 _TYPE_TXT : "txt",
180 180 _TYPE_AAAA : "quada",
181 181 _TYPE_SRV : "srv",
182 182 _TYPE_ANY : "any" }
183 183
184 184 # utility functions
185 185
186 186 def currentTimeMillis():
187 187 """Current system time in milliseconds"""
188 188 return time.time() * 1000
189 189
190 190 # Exceptions
191 191
192 192 class NonLocalNameException(Exception):
193 193 pass
194 194
195 195 class NonUniqueNameException(Exception):
196 196 pass
197 197
198 198 class NamePartTooLongException(Exception):
199 199 pass
200 200
201 201 class AbstractMethodException(Exception):
202 202 pass
203 203
204 204 class BadTypeInNameException(Exception):
205 205 pass
206 206
207 207 class BadDomainName(Exception):
208 208 def __init__(self, pos):
209 209 Exception.__init__(self, "at position %s" % pos)
210 210
211 211 class BadDomainNameCircular(BadDomainName):
212 212 pass
213 213
214 214 # implementation classes
215 215
216 216 class DNSEntry(object):
217 217 """A DNS entry"""
218 218
219 219 def __init__(self, name, type, clazz):
220 220 self.key = string.lower(name)
221 221 self.name = name
222 222 self.type = type
223 223 self.clazz = clazz & _CLASS_MASK
224 224 self.unique = (clazz & _CLASS_UNIQUE) != 0
225 225
226 226 def __eq__(self, other):
227 227 """Equality test on name, type, and class"""
228 228 if isinstance(other, DNSEntry):
229 229 return self.name == other.name and self.type == other.type and self.clazz == other.clazz
230 230 return 0
231 231
232 232 def __ne__(self, other):
233 233 """Non-equality test"""
234 234 return not self.__eq__(other)
235 235
236 236 def getClazz(self, clazz):
237 237 """Class accessor"""
238 238 try:
239 239 return _CLASSES[clazz]
240 240 except KeyError:
241 241 return "?(%s)" % (clazz)
242 242
243 243 def getType(self, type):
244 244 """Type accessor"""
245 245 try:
246 246 return _TYPES[type]
247 247 except KeyError:
248 248 return "?(%s)" % (type)
249 249
250 250 def toString(self, hdr, other):
251 251 """String representation with additional information"""
252 252 result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
253 253 if self.unique:
254 254 result += "-unique,"
255 255 else:
256 256 result += ","
257 257 result += self.name
258 258 if other is not None:
259 259 result += ",%s]" % (other)
260 260 else:
261 261 result += "]"
262 262 return result
263 263
264 264 class DNSQuestion(DNSEntry):
265 265 """A DNS question entry"""
266 266
267 267 def __init__(self, name, type, clazz):
268 268 if not name.endswith(".local."):
269 269 raise NonLocalNameException(name)
270 270 DNSEntry.__init__(self, name, type, clazz)
271 271
272 272 def answeredBy(self, rec):
273 273 """Returns true if the question is answered by the record"""
274 274 return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
275 275
276 276 def __repr__(self):
277 277 """String representation"""
278 278 return DNSEntry.toString(self, "question", None)
279 279
280 280
281 281 class DNSRecord(DNSEntry):
282 282 """A DNS record - like a DNS entry, but has a TTL"""
283 283
284 284 def __init__(self, name, type, clazz, ttl):
285 285 DNSEntry.__init__(self, name, type, clazz)
286 286 self.ttl = ttl
287 287 self.created = currentTimeMillis()
288 288
289 289 def __eq__(self, other):
290 290 """Tests equality as per DNSRecord"""
291 291 if isinstance(other, DNSRecord):
292 292 return DNSEntry.__eq__(self, other)
293 293 return 0
294 294
295 295 def suppressedBy(self, msg):
296 296 """Returns true if any answer in a message can suffice for the
297 297 information held in this record."""
298 298 for record in msg.answers:
299 299 if self.suppressedByAnswer(record):
300 300 return 1
301 301 return 0
302 302
303 303 def suppressedByAnswer(self, other):
304 304 """Returns true if another record has same name, type and class,
305 305 and if its TTL is at least half of this record's."""
306 306 if self == other and other.ttl > (self.ttl / 2):
307 307 return 1
308 308 return 0
309 309
310 310 def getExpirationTime(self, percent):
311 311 """Returns the time at which this record will have expired
312 312 by a certain percentage."""
313 313 return self.created + (percent * self.ttl * 10)
314 314
315 315 def getRemainingTTL(self, now):
316 316 """Returns the remaining TTL in seconds."""
317 317 return max(0, (self.getExpirationTime(100) - now) / 1000)
318 318
319 319 def isExpired(self, now):
320 320 """Returns true if this record has expired."""
321 321 return self.getExpirationTime(100) <= now
322 322
323 323 def isStale(self, now):
324 324 """Returns true if this record is at least half way expired."""
325 325 return self.getExpirationTime(50) <= now
326 326
327 327 def resetTTL(self, other):
328 328 """Sets this record's TTL and created time to that of
329 329 another record."""
330 330 self.created = other.created
331 331 self.ttl = other.ttl
332 332
333 333 def write(self, out):
334 334 """Abstract method"""
335 335 raise AbstractMethodException
336 336
337 337 def toString(self, other):
338 338 """String representation with additional information"""
339 339 arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
340 340 return DNSEntry.toString(self, "record", arg)
341 341
342 342 class DNSAddress(DNSRecord):
343 343 """A DNS address record"""
344 344
345 345 def __init__(self, name, type, clazz, ttl, address):
346 346 DNSRecord.__init__(self, name, type, clazz, ttl)
347 347 self.address = address
348 348
349 349 def write(self, out):
350 350 """Used in constructing an outgoing packet"""
351 351 out.writeString(self.address, len(self.address))
352 352
353 353 def __eq__(self, other):
354 354 """Tests equality on address"""
355 355 if isinstance(other, DNSAddress):
356 356 return self.address == other.address
357 357 return 0
358 358
359 359 def __repr__(self):
360 360 """String representation"""
361 361 try:
362 362 return socket.inet_ntoa(self.address)
363 363 except Exception:
364 364 return self.address
365 365
366 366 class DNSHinfo(DNSRecord):
367 367 """A DNS host information record"""
368 368
369 369 def __init__(self, name, type, clazz, ttl, cpu, os):
370 370 DNSRecord.__init__(self, name, type, clazz, ttl)
371 371 self.cpu = cpu
372 372 self.os = os
373 373
374 374 def write(self, out):
375 375 """Used in constructing an outgoing packet"""
376 376 out.writeString(self.cpu, len(self.cpu))
377 377 out.writeString(self.os, len(self.os))
378 378
379 379 def __eq__(self, other):
380 380 """Tests equality on cpu and os"""
381 381 if isinstance(other, DNSHinfo):
382 382 return self.cpu == other.cpu and self.os == other.os
383 383 return 0
384 384
385 385 def __repr__(self):
386 386 """String representation"""
387 387 return self.cpu + " " + self.os
388 388
389 389 class DNSPointer(DNSRecord):
390 390 """A DNS pointer record"""
391 391
392 392 def __init__(self, name, type, clazz, ttl, alias):
393 393 DNSRecord.__init__(self, name, type, clazz, ttl)
394 394 self.alias = alias
395 395
396 396 def write(self, out):
397 397 """Used in constructing an outgoing packet"""
398 398 out.writeName(self.alias)
399 399
400 400 def __eq__(self, other):
401 401 """Tests equality on alias"""
402 402 if isinstance(other, DNSPointer):
403 403 return self.alias == other.alias
404 404 return 0
405 405
406 406 def __repr__(self):
407 407 """String representation"""
408 408 return self.toString(self.alias)
409 409
410 410 class DNSText(DNSRecord):
411 411 """A DNS text record"""
412 412
413 413 def __init__(self, name, type, clazz, ttl, text):
414 414 DNSRecord.__init__(self, name, type, clazz, ttl)
415 415 self.text = text
416 416
417 417 def write(self, out):
418 418 """Used in constructing an outgoing packet"""
419 419 out.writeString(self.text, len(self.text))
420 420
421 421 def __eq__(self, other):
422 422 """Tests equality on text"""
423 423 if isinstance(other, DNSText):
424 424 return self.text == other.text
425 425 return 0
426 426
427 427 def __repr__(self):
428 428 """String representation"""
429 429 if len(self.text) > 10:
430 430 return self.toString(self.text[:7] + "...")
431 431 else:
432 432 return self.toString(self.text)
433 433
434 434 class DNSService(DNSRecord):
435 435 """A DNS service record"""
436 436
437 437 def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
438 438 DNSRecord.__init__(self, name, type, clazz, ttl)
439 439 self.priority = priority
440 440 self.weight = weight
441 441 self.port = port
442 442 self.server = server
443 443
444 444 def write(self, out):
445 445 """Used in constructing an outgoing packet"""
446 446 out.writeShort(self.priority)
447 447 out.writeShort(self.weight)
448 448 out.writeShort(self.port)
449 449 out.writeName(self.server)
450 450
451 451 def __eq__(self, other):
452 452 """Tests equality on priority, weight, port and server"""
453 453 if isinstance(other, DNSService):
454 454 return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
455 455 return 0
456 456
457 457 def __repr__(self):
458 458 """String representation"""
459 459 return self.toString("%s:%s" % (self.server, self.port))
460 460
461 461 class DNSIncoming(object):
462 462 """Object representation of an incoming DNS packet"""
463 463
464 464 def __init__(self, data):
465 465 """Constructor from string holding bytes of packet"""
466 466 self.offset = 0
467 467 self.data = data
468 468 self.questions = []
469 469 self.answers = []
470 470 self.numQuestions = 0
471 471 self.numAnswers = 0
472 472 self.numAuthorities = 0
473 473 self.numAdditionals = 0
474 474
475 475 self.readHeader()
476 476 self.readQuestions()
477 477 self.readOthers()
478 478
479 479 def readHeader(self):
480 480 """Reads header portion of packet"""
481 481 format = '!HHHHHH'
482 482 length = struct.calcsize(format)
483 483 info = struct.unpack(format, self.data[self.offset:self.offset+length])
484 484 self.offset += length
485 485
486 486 self.id = info[0]
487 487 self.flags = info[1]
488 488 self.numQuestions = info[2]
489 489 self.numAnswers = info[3]
490 490 self.numAuthorities = info[4]
491 491 self.numAdditionals = info[5]
492 492
493 493 def readQuestions(self):
494 494 """Reads questions section of packet"""
495 495 format = '!HH'
496 496 length = struct.calcsize(format)
497 497 for i in range(0, self.numQuestions):
498 498 name = self.readName()
499 499 info = struct.unpack(format, self.data[self.offset:self.offset+length])
500 500 self.offset += length
501 501
502 502 try:
503 503 question = DNSQuestion(name, info[0], info[1])
504 504 self.questions.append(question)
505 505 except NonLocalNameException:
506 506 pass
507 507
508 508 def readInt(self):
509 509 """Reads an integer from the packet"""
510 510 format = '!I'
511 511 length = struct.calcsize(format)
512 512 info = struct.unpack(format, self.data[self.offset:self.offset+length])
513 513 self.offset += length
514 514 return info[0]
515 515
516 516 def readCharacterString(self):
517 517 """Reads a character string from the packet"""
518 518 length = ord(self.data[self.offset])
519 519 self.offset += 1
520 520 return self.readString(length)
521 521
522 522 def readString(self, len):
523 523 """Reads a string of a given length from the packet"""
524 524 format = '!' + str(len) + 's'
525 525 length = struct.calcsize(format)
526 526 info = struct.unpack(format, self.data[self.offset:self.offset+length])
527 527 self.offset += length
528 528 return info[0]
529 529
530 530 def readUnsignedShort(self):
531 531 """Reads an unsigned short from the packet"""
532 532 format = '!H'
533 533 length = struct.calcsize(format)
534 534 info = struct.unpack(format, self.data[self.offset:self.offset+length])
535 535 self.offset += length
536 536 return info[0]
537 537
538 538 def readOthers(self):
539 539 """Reads the answers, authorities and additionals section of the packet"""
540 540 format = '!HHiH'
541 541 length = struct.calcsize(format)
542 542 n = self.numAnswers + self.numAuthorities + self.numAdditionals
543 543 for i in range(0, n):
544 544 domain = self.readName()
545 545 info = struct.unpack(format, self.data[self.offset:self.offset+length])
546 546 self.offset += length
547 547
548 548 rec = None
549 549 if info[0] == _TYPE_A:
550 550 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
551 551 elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
552 552 rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
553 553 elif info[0] == _TYPE_TXT:
554 554 rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
555 555 elif info[0] == _TYPE_SRV:
556 556 rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
557 557 elif info[0] == _TYPE_HINFO:
558 558 rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
559 559 elif info[0] == _TYPE_AAAA:
560 560 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
561 561 else:
562 562 # Try to ignore types we don't know about
563 563 # this may mean the rest of the name is
564 564 # unable to be parsed, and may show errors
565 565 # so this is left for debugging. New types
566 566 # encountered need to be parsed properly.
567 567 #
568 568 #print "UNKNOWN TYPE = " + str(info[0])
569 569 #raise BadTypeInNameException
570 570 self.offset += info[3]
571 571
572 572 if rec is not None:
573 573 self.answers.append(rec)
574 574
575 575 def isQuery(self):
576 576 """Returns true if this is a query"""
577 577 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
578 578
579 579 def isResponse(self):
580 580 """Returns true if this is a response"""
581 581 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
582 582
583 583 def readUTF(self, offset, len):
584 584 """Reads a UTF-8 string of a given length from the packet"""
585 585 return self.data[offset:offset+len].decode('utf-8')
586 586
587 587 def readName(self):
588 588 """Reads a domain name from the packet"""
589 589 result = ''
590 590 off = self.offset
591 591 next = -1
592 592 first = off
593 593
594 594 while True:
595 595 len = ord(self.data[off])
596 596 off += 1
597 597 if len == 0:
598 598 break
599 599 t = len & 0xC0
600 600 if t == 0x00:
601 601 result = ''.join((result, self.readUTF(off, len) + '.'))
602 602 off += len
603 603 elif t == 0xC0:
604 604 if next < 0:
605 605 next = off + 1
606 606 off = ((len & 0x3F) << 8) | ord(self.data[off])
607 607 if off >= first:
608 608 raise BadDomainNameCircular(off)
609 609 first = off
610 610 else:
611 611 raise BadDomainName(off)
612 612
613 613 if next >= 0:
614 614 self.offset = next
615 615 else:
616 616 self.offset = off
617 617
618 618 return result
619 619
620 620
621 621 class DNSOutgoing(object):
622 622 """Object representation of an outgoing packet"""
623 623
624 624 def __init__(self, flags, multicast = 1):
625 625 self.finished = 0
626 626 self.id = 0
627 627 self.multicast = multicast
628 628 self.flags = flags
629 629 self.names = {}
630 630 self.data = []
631 631 self.size = 12
632 632
633 633 self.questions = []
634 634 self.answers = []
635 635 self.authorities = []
636 636 self.additionals = []
637 637
638 638 def addQuestion(self, record):
639 639 """Adds a question"""
640 640 self.questions.append(record)
641 641
642 642 def addAnswer(self, inp, record):
643 643 """Adds an answer"""
644 644 if not record.suppressedBy(inp):
645 645 self.addAnswerAtTime(record, 0)
646 646
647 647 def addAnswerAtTime(self, record, now):
648 648 """Adds an answer if if does not expire by a certain time"""
649 649 if record is not None:
650 650 if now == 0 or not record.isExpired(now):
651 651 self.answers.append((record, now))
652 652
653 def addAuthorativeAnswer(self, record):
653 def addAuthoritativeAnswer(self, record):
654 654 """Adds an authoritative answer"""
655 655 self.authorities.append(record)
656 656
657 657 def addAdditionalAnswer(self, record):
658 658 """Adds an additional answer"""
659 659 self.additionals.append(record)
660 660
661 661 def writeByte(self, value):
662 662 """Writes a single byte to the packet"""
663 663 format = '!c'
664 664 self.data.append(struct.pack(format, chr(value)))
665 665 self.size += 1
666 666
667 667 def insertShort(self, index, value):
668 668 """Inserts an unsigned short in a certain position in the packet"""
669 669 format = '!H'
670 670 self.data.insert(index, struct.pack(format, value))
671 671 self.size += 2
672 672
673 673 def writeShort(self, value):
674 674 """Writes an unsigned short to the packet"""
675 675 format = '!H'
676 676 self.data.append(struct.pack(format, value))
677 677 self.size += 2
678 678
679 679 def writeInt(self, value):
680 680 """Writes an unsigned integer to the packet"""
681 681 format = '!I'
682 682 self.data.append(struct.pack(format, int(value)))
683 683 self.size += 4
684 684
685 685 def writeString(self, value, length):
686 686 """Writes a string to the packet"""
687 687 format = '!' + str(length) + 's'
688 688 self.data.append(struct.pack(format, value))
689 689 self.size += length
690 690
691 691 def writeUTF(self, s):
692 692 """Writes a UTF-8 string of a given length to the packet"""
693 693 utfstr = s.encode('utf-8')
694 694 length = len(utfstr)
695 695 if length > 64:
696 696 raise NamePartTooLongException
697 697 self.writeByte(length)
698 698 self.writeString(utfstr, length)
699 699
700 700 def writeName(self, name):
701 701 """Writes a domain name to the packet"""
702 702
703 703 try:
704 704 # Find existing instance of this name in packet
705 705 #
706 706 index = self.names[name]
707 707 except KeyError:
708 708 # No record of this name already, so write it
709 709 # out as normal, recording the location of the name
710 710 # for future pointers to it.
711 711 #
712 712 self.names[name] = self.size
713 713 parts = name.split('.')
714 714 if parts[-1] == '':
715 715 parts = parts[:-1]
716 716 for part in parts:
717 717 self.writeUTF(part)
718 718 self.writeByte(0)
719 719 return
720 720
721 721 # An index was found, so write a pointer to it
722 722 #
723 723 self.writeByte((index >> 8) | 0xC0)
724 724 self.writeByte(index)
725 725
726 726 def writeQuestion(self, question):
727 727 """Writes a question to the packet"""
728 728 self.writeName(question.name)
729 729 self.writeShort(question.type)
730 730 self.writeShort(question.clazz)
731 731
732 732 def writeRecord(self, record, now):
733 733 """Writes a record (answer, authoritative answer, additional) to
734 734 the packet"""
735 735 self.writeName(record.name)
736 736 self.writeShort(record.type)
737 737 if record.unique and self.multicast:
738 738 self.writeShort(record.clazz | _CLASS_UNIQUE)
739 739 else:
740 740 self.writeShort(record.clazz)
741 741 if now == 0:
742 742 self.writeInt(record.ttl)
743 743 else:
744 744 self.writeInt(record.getRemainingTTL(now))
745 745 index = len(self.data)
746 746 # Adjust size for the short we will write before this record
747 747 #
748 748 self.size += 2
749 749 record.write(self)
750 750 self.size -= 2
751 751
752 752 length = len(''.join(self.data[index:]))
753 753 self.insertShort(index, length) # Here is the short we adjusted for
754 754
755 755 def packet(self):
756 756 """Returns a string containing the packet's bytes
757 757
758 758 No further parts should be added to the packet once this
759 759 is done."""
760 760 if not self.finished:
761 761 self.finished = 1
762 762 for question in self.questions:
763 763 self.writeQuestion(question)
764 764 for answer, time in self.answers:
765 765 self.writeRecord(answer, time)
766 766 for authority in self.authorities:
767 767 self.writeRecord(authority, 0)
768 768 for additional in self.additionals:
769 769 self.writeRecord(additional, 0)
770 770
771 771 self.insertShort(0, len(self.additionals))
772 772 self.insertShort(0, len(self.authorities))
773 773 self.insertShort(0, len(self.answers))
774 774 self.insertShort(0, len(self.questions))
775 775 self.insertShort(0, self.flags)
776 776 if self.multicast:
777 777 self.insertShort(0, 0)
778 778 else:
779 779 self.insertShort(0, self.id)
780 780 return ''.join(self.data)
781 781
782 782
783 783 class DNSCache(object):
784 784 """A cache of DNS entries"""
785 785
786 786 def __init__(self):
787 787 self.cache = {}
788 788
789 789 def add(self, entry):
790 790 """Adds an entry"""
791 791 try:
792 792 list = self.cache[entry.key]
793 793 except KeyError:
794 794 list = self.cache[entry.key] = []
795 795 list.append(entry)
796 796
797 797 def remove(self, entry):
798 798 """Removes an entry"""
799 799 try:
800 800 list = self.cache[entry.key]
801 801 list.remove(entry)
802 802 except KeyError:
803 803 pass
804 804
805 805 def get(self, entry):
806 806 """Gets an entry by key. Will return None if there is no
807 807 matching entry."""
808 808 try:
809 809 list = self.cache[entry.key]
810 810 return list[list.index(entry)]
811 811 except (KeyError, ValueError):
812 812 return None
813 813
814 814 def getByDetails(self, name, type, clazz):
815 815 """Gets an entry by details. Will return None if there is
816 816 no matching entry."""
817 817 entry = DNSEntry(name, type, clazz)
818 818 return self.get(entry)
819 819
820 820 def entriesWithName(self, name):
821 821 """Returns a list of entries whose key matches the name."""
822 822 try:
823 823 return self.cache[name]
824 824 except KeyError:
825 825 return []
826 826
827 827 def entries(self):
828 828 """Returns a list of all entries"""
829 829 def add(x, y): return x+y
830 830 try:
831 831 return reduce(add, self.cache.values())
832 832 except Exception:
833 833 return []
834 834
835 835
836 836 class Engine(threading.Thread):
837 837 """An engine wraps read access to sockets, allowing objects that
838 838 need to receive data from sockets to be called back when the
839 839 sockets are ready.
840 840
841 841 A reader needs a handle_read() method, which is called when the socket
842 842 it is interested in is ready for reading.
843 843
844 844 Writers are not implemented here, because we only send short
845 845 packets.
846 846 """
847 847
848 848 def __init__(self, zeroconf):
849 849 threading.Thread.__init__(self)
850 850 self.zeroconf = zeroconf
851 851 self.readers = {} # maps socket to reader
852 852 self.timeout = 5
853 853 self.condition = threading.Condition()
854 854 self.start()
855 855
856 856 def run(self):
857 857 while not globals()['_GLOBAL_DONE']:
858 858 rs = self.getReaders()
859 859 if len(rs) == 0:
860 860 # No sockets to manage, but we wait for the timeout
861 861 # or addition of a socket
862 862 #
863 863 self.condition.acquire()
864 864 self.condition.wait(self.timeout)
865 865 self.condition.release()
866 866 else:
867 867 try:
868 868 rr, wr, er = select.select(rs, [], [], self.timeout)
869 869 for socket in rr:
870 870 try:
871 871 self.readers[socket].handle_read()
872 872 except Exception:
873 873 if not globals()['_GLOBAL_DONE']:
874 874 traceback.print_exc()
875 875 except Exception:
876 876 pass
877 877
878 878 def getReaders(self):
879 879 self.condition.acquire()
880 880 result = self.readers.keys()
881 881 self.condition.release()
882 882 return result
883 883
884 884 def addReader(self, reader, socket):
885 885 self.condition.acquire()
886 886 self.readers[socket] = reader
887 887 self.condition.notify()
888 888 self.condition.release()
889 889
890 890 def delReader(self, socket):
891 891 self.condition.acquire()
892 892 del(self.readers[socket])
893 893 self.condition.notify()
894 894 self.condition.release()
895 895
896 896 def notify(self):
897 897 self.condition.acquire()
898 898 self.condition.notify()
899 899 self.condition.release()
900 900
901 901 class Listener(object):
902 902 """A Listener is used by this module to listen on the multicast
903 903 group to which DNS messages are sent, allowing the implementation
904 904 to cache information as it arrives.
905 905
906 906 It requires registration with an Engine object in order to have
907 907 the read() method called when a socket is available for reading."""
908 908
909 909 def __init__(self, zeroconf):
910 910 self.zeroconf = zeroconf
911 911 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
912 912
913 913 def handle_read(self):
914 914 data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
915 915 self.data = data
916 916 msg = DNSIncoming(data)
917 917 if msg.isQuery():
918 918 # Always multicast responses
919 919 #
920 920 if port == _MDNS_PORT:
921 921 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
922 922 # If it's not a multicast query, reply via unicast
923 923 # and multicast
924 924 #
925 925 elif port == _DNS_PORT:
926 926 self.zeroconf.handleQuery(msg, addr, port)
927 927 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
928 928 else:
929 929 self.zeroconf.handleResponse(msg)
930 930
931 931
932 932 class Reaper(threading.Thread):
933 933 """A Reaper is used by this module to remove cache entries that
934 934 have expired."""
935 935
936 936 def __init__(self, zeroconf):
937 937 threading.Thread.__init__(self)
938 938 self.zeroconf = zeroconf
939 939 self.start()
940 940
941 941 def run(self):
942 942 while True:
943 943 self.zeroconf.wait(10 * 1000)
944 944 if globals()['_GLOBAL_DONE']:
945 945 return
946 946 now = currentTimeMillis()
947 947 for record in self.zeroconf.cache.entries():
948 948 if record.isExpired(now):
949 949 self.zeroconf.updateRecord(now, record)
950 950 self.zeroconf.cache.remove(record)
951 951
952 952
953 953 class ServiceBrowser(threading.Thread):
954 954 """Used to browse for a service of a specific type.
955 955
956 956 The listener object will have its addService() and
957 957 removeService() methods called when this browser
958 958 discovers changes in the services availability."""
959 959
960 960 def __init__(self, zeroconf, type, listener):
961 961 """Creates a browser for a specific type"""
962 962 threading.Thread.__init__(self)
963 963 self.zeroconf = zeroconf
964 964 self.type = type
965 965 self.listener = listener
966 966 self.services = {}
967 967 self.nextTime = currentTimeMillis()
968 968 self.delay = _BROWSER_TIME
969 969 self.list = []
970 970
971 971 self.done = 0
972 972
973 973 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
974 974 self.start()
975 975
976 976 def updateRecord(self, zeroconf, now, record):
977 977 """Callback invoked by Zeroconf when new information arrives.
978 978
979 979 Updates information required by browser in the Zeroconf cache."""
980 980 if record.type == _TYPE_PTR and record.name == self.type:
981 981 expired = record.isExpired(now)
982 982 try:
983 983 oldrecord = self.services[record.alias.lower()]
984 984 if not expired:
985 985 oldrecord.resetTTL(record)
986 986 else:
987 987 del(self.services[record.alias.lower()])
988 988 callback = lambda x: self.listener.removeService(x, self.type, record.alias)
989 989 self.list.append(callback)
990 990 return
991 991 except Exception:
992 992 if not expired:
993 993 self.services[record.alias.lower()] = record
994 994 callback = lambda x: self.listener.addService(x, self.type, record.alias)
995 995 self.list.append(callback)
996 996
997 997 expires = record.getExpirationTime(75)
998 998 if expires < self.nextTime:
999 999 self.nextTime = expires
1000 1000
1001 1001 def cancel(self):
1002 1002 self.done = 1
1003 1003 self.zeroconf.notifyAll()
1004 1004
1005 1005 def run(self):
1006 1006 while True:
1007 1007 event = None
1008 1008 now = currentTimeMillis()
1009 1009 if len(self.list) == 0 and self.nextTime > now:
1010 1010 self.zeroconf.wait(self.nextTime - now)
1011 1011 if globals()['_GLOBAL_DONE'] or self.done:
1012 1012 return
1013 1013 now = currentTimeMillis()
1014 1014
1015 1015 if self.nextTime <= now:
1016 1016 out = DNSOutgoing(_FLAGS_QR_QUERY)
1017 1017 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1018 1018 for record in self.services.values():
1019 1019 if not record.isExpired(now):
1020 1020 out.addAnswerAtTime(record, now)
1021 1021 self.zeroconf.send(out)
1022 1022 self.nextTime = now + self.delay
1023 1023 self.delay = min(20 * 1000, self.delay * 2)
1024 1024
1025 1025 if len(self.list) > 0:
1026 1026 event = self.list.pop(0)
1027 1027
1028 1028 if event is not None:
1029 1029 event(self.zeroconf)
1030 1030
1031 1031
1032 1032 class ServiceInfo(object):
1033 1033 """Service information"""
1034 1034
1035 1035 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
1036 1036 """Create a service description.
1037 1037
1038 1038 type: fully qualified service type name
1039 1039 name: fully qualified service name
1040 1040 address: IP address as unsigned short, network byte order
1041 1041 port: port that the service runs on
1042 1042 weight: weight of the service
1043 1043 priority: priority of the service
1044 1044 properties: dictionary of properties (or a string holding the bytes for the text field)
1045 1045 server: fully qualified name for service host (defaults to name)"""
1046 1046
1047 1047 if not name.endswith(type):
1048 1048 raise BadTypeInNameException
1049 1049 self.type = type
1050 1050 self.name = name
1051 1051 self.address = address
1052 1052 self.port = port
1053 1053 self.weight = weight
1054 1054 self.priority = priority
1055 1055 if server:
1056 1056 self.server = server
1057 1057 else:
1058 1058 self.server = name
1059 1059 self.setProperties(properties)
1060 1060
1061 1061 def setProperties(self, properties):
1062 1062 """Sets properties and text of this info from a dictionary"""
1063 1063 if isinstance(properties, dict):
1064 1064 self.properties = properties
1065 1065 list = []
1066 1066 result = ''
1067 1067 for key in properties:
1068 1068 value = properties[key]
1069 1069 if value is None:
1070 1070 suffix = ''
1071 1071 elif isinstance(value, str):
1072 1072 suffix = value
1073 1073 elif isinstance(value, int):
1074 1074 if value:
1075 1075 suffix = 'true'
1076 1076 else:
1077 1077 suffix = 'false'
1078 1078 else:
1079 1079 suffix = ''
1080 1080 list.append('='.join((key, suffix)))
1081 1081 for item in list:
1082 1082 result = ''.join((result, struct.pack('!c', chr(len(item))), item))
1083 1083 self.text = result
1084 1084 else:
1085 1085 self.text = properties
1086 1086
1087 1087 def setText(self, text):
1088 1088 """Sets properties and text given a text field"""
1089 1089 self.text = text
1090 1090 try:
1091 1091 result = {}
1092 1092 end = len(text)
1093 1093 index = 0
1094 1094 strs = []
1095 1095 while index < end:
1096 1096 length = ord(text[index])
1097 1097 index += 1
1098 1098 strs.append(text[index:index+length])
1099 1099 index += length
1100 1100
1101 1101 for s in strs:
1102 1102 eindex = s.find('=')
1103 1103 if eindex == -1:
1104 1104 # No equals sign at all
1105 1105 key = s
1106 1106 value = 0
1107 1107 else:
1108 1108 key = s[:eindex]
1109 1109 value = s[eindex+1:]
1110 1110 if value == 'true':
1111 1111 value = 1
1112 1112 elif value == 'false' or not value:
1113 1113 value = 0
1114 1114
1115 1115 # Only update non-existent properties
1116 1116 if key and result.get(key) == None:
1117 1117 result[key] = value
1118 1118
1119 1119 self.properties = result
1120 1120 except Exception:
1121 1121 traceback.print_exc()
1122 1122 self.properties = None
1123 1123
1124 1124 def getType(self):
1125 1125 """Type accessor"""
1126 1126 return self.type
1127 1127
1128 1128 def getName(self):
1129 1129 """Name accessor"""
1130 1130 if self.type is not None and self.name.endswith("." + self.type):
1131 1131 return self.name[:len(self.name) - len(self.type) - 1]
1132 1132 return self.name
1133 1133
1134 1134 def getAddress(self):
1135 1135 """Address accessor"""
1136 1136 return self.address
1137 1137
1138 1138 def getPort(self):
1139 1139 """Port accessor"""
1140 1140 return self.port
1141 1141
1142 1142 def getPriority(self):
1143 1143 """Priority accessor"""
1144 1144 return self.priority
1145 1145
1146 1146 def getWeight(self):
1147 1147 """Weight accessor"""
1148 1148 return self.weight
1149 1149
1150 1150 def getProperties(self):
1151 1151 """Properties accessor"""
1152 1152 return self.properties
1153 1153
1154 1154 def getText(self):
1155 1155 """Text accessor"""
1156 1156 return self.text
1157 1157
1158 1158 def getServer(self):
1159 1159 """Server accessor"""
1160 1160 return self.server
1161 1161
1162 1162 def updateRecord(self, zeroconf, now, record):
1163 1163 """Updates service information from a DNS record"""
1164 1164 if record is not None and not record.isExpired(now):
1165 1165 if record.type == _TYPE_A:
1166 1166 #if record.name == self.name:
1167 1167 if record.name == self.server:
1168 1168 self.address = record.address
1169 1169 elif record.type == _TYPE_SRV:
1170 1170 if record.name == self.name:
1171 1171 self.server = record.server
1172 1172 self.port = record.port
1173 1173 self.weight = record.weight
1174 1174 self.priority = record.priority
1175 1175 #self.address = None
1176 1176 self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
1177 1177 elif record.type == _TYPE_TXT:
1178 1178 if record.name == self.name:
1179 1179 self.setText(record.text)
1180 1180
1181 1181 def request(self, zeroconf, timeout):
1182 1182 """Returns true if the service could be discovered on the
1183 1183 network, and updates this object with details discovered.
1184 1184 """
1185 1185 now = currentTimeMillis()
1186 1186 delay = _LISTENER_TIME
1187 1187 next = now + delay
1188 1188 last = now + timeout
1189 1189 result = 0
1190 1190 try:
1191 1191 zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
1192 1192 while self.server is None or self.address is None or self.text is None:
1193 1193 if last <= now:
1194 1194 return 0
1195 1195 if next <= now:
1196 1196 out = DNSOutgoing(_FLAGS_QR_QUERY)
1197 1197 out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
1198 1198 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
1199 1199 out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
1200 1200 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
1201 1201 if self.server is not None:
1202 1202 out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
1203 1203 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
1204 1204 zeroconf.send(out)
1205 1205 next = now + delay
1206 1206 delay = delay * 2
1207 1207
1208 1208 zeroconf.wait(min(next, last) - now)
1209 1209 now = currentTimeMillis()
1210 1210 result = 1
1211 1211 finally:
1212 1212 zeroconf.removeListener(self)
1213 1213
1214 1214 return result
1215 1215
1216 1216 def __eq__(self, other):
1217 1217 """Tests equality of service name"""
1218 1218 if isinstance(other, ServiceInfo):
1219 1219 return other.name == self.name
1220 1220 return 0
1221 1221
1222 1222 def __ne__(self, other):
1223 1223 """Non-equality test"""
1224 1224 return not self.__eq__(other)
1225 1225
1226 1226 def __repr__(self):
1227 1227 """String representation"""
1228 1228 result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
1229 1229 if self.text is None:
1230 1230 result += "None"
1231 1231 else:
1232 1232 if len(self.text) < 20:
1233 1233 result += self.text
1234 1234 else:
1235 1235 result += self.text[:17] + "..."
1236 1236 result += "]"
1237 1237 return result
1238 1238
1239 1239
1240 1240 class Zeroconf(object):
1241 1241 """Implementation of Zeroconf Multicast DNS Service Discovery
1242 1242
1243 1243 Supports registration, unregistration, queries and browsing.
1244 1244 """
1245 1245 def __init__(self, bindaddress=None):
1246 1246 """Creates an instance of the Zeroconf class, establishing
1247 1247 multicast communications, listening and reaping threads."""
1248 1248 globals()['_GLOBAL_DONE'] = 0
1249 1249 if bindaddress is None:
1250 1250 self.intf = socket.gethostbyname(socket.gethostname())
1251 1251 else:
1252 1252 self.intf = bindaddress
1253 1253 self.group = ('', _MDNS_PORT)
1254 1254 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1255 1255 try:
1256 1256 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1257 1257 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1258 1258 except Exception:
1259 1259 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1260 1260 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1261 1261 # Volume 2"), but some BSD-derived systems require
1262 1262 # SO_REUSEPORT to be specified explicitly. Also, not all
1263 1263 # versions of Python have SO_REUSEPORT available. So
1264 1264 # if you're on a BSD-based system, and haven't upgraded
1265 1265 # to Python 2.3 yet, you may find this library doesn't
1266 1266 # work as expected.
1267 1267 #
1268 1268 pass
1269 1269 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
1270 1270 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
1271 1271 try:
1272 1272 self.socket.bind(self.group)
1273 1273 except Exception:
1274 1274 # Some versions of linux raise an exception even though
1275 1275 # the SO_REUSE* options have been set, so ignore it
1276 1276 #
1277 1277 pass
1278 1278 #self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
1279 1279 self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1280 1280
1281 1281 self.listeners = []
1282 1282 self.browsers = []
1283 1283 self.services = {}
1284 1284 self.servicetypes = {}
1285 1285
1286 1286 self.cache = DNSCache()
1287 1287
1288 1288 self.condition = threading.Condition()
1289 1289
1290 1290 self.engine = Engine(self)
1291 1291 self.listener = Listener(self)
1292 1292 self.reaper = Reaper(self)
1293 1293
1294 1294 def isLoopback(self):
1295 1295 return self.intf.startswith("127.0.0.1")
1296 1296
1297 1297 def isLinklocal(self):
1298 1298 return self.intf.startswith("169.254.")
1299 1299
1300 1300 def wait(self, timeout):
1301 1301 """Calling thread waits for a given number of milliseconds or
1302 1302 until notified."""
1303 1303 self.condition.acquire()
1304 1304 self.condition.wait(timeout/1000)
1305 1305 self.condition.release()
1306 1306
1307 1307 def notifyAll(self):
1308 1308 """Notifies all waiting threads"""
1309 1309 self.condition.acquire()
1310 1310 self.condition.notifyAll()
1311 1311 self.condition.release()
1312 1312
1313 1313 def getServiceInfo(self, type, name, timeout=3000):
1314 1314 """Returns network's service information for a particular
1315 1315 name and type, or None if no service matches by the timeout,
1316 1316 which defaults to 3 seconds."""
1317 1317 info = ServiceInfo(type, name)
1318 1318 if info.request(self, timeout):
1319 1319 return info
1320 1320 return None
1321 1321
1322 1322 def addServiceListener(self, type, listener):
1323 1323 """Adds a listener for a particular service type. This object
1324 1324 will then have its updateRecord method called when information
1325 1325 arrives for that type."""
1326 1326 self.removeServiceListener(listener)
1327 1327 self.browsers.append(ServiceBrowser(self, type, listener))
1328 1328
1329 1329 def removeServiceListener(self, listener):
1330 1330 """Removes a listener from the set that is currently listening."""
1331 1331 for browser in self.browsers:
1332 1332 if browser.listener == listener:
1333 1333 browser.cancel()
1334 1334 del(browser)
1335 1335
1336 1336 def registerService(self, info, ttl=_DNS_TTL):
1337 1337 """Registers service information to the network with a default TTL
1338 1338 of 60 seconds. Zeroconf will then respond to requests for
1339 1339 information for that service. The name of the service may be
1340 1340 changed if needed to make it unique on the network."""
1341 1341 self.checkService(info)
1342 1342 self.services[info.name.lower()] = info
1343 1343 if self.servicetypes.has_key(info.type):
1344 1344 self.servicetypes[info.type]+=1
1345 1345 else:
1346 1346 self.servicetypes[info.type]=1
1347 1347 now = currentTimeMillis()
1348 1348 nextTime = now
1349 1349 i = 0
1350 1350 while i < 3:
1351 1351 if now < nextTime:
1352 1352 self.wait(nextTime - now)
1353 1353 now = currentTimeMillis()
1354 1354 continue
1355 1355 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1356 1356 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
1357 1357 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
1358 1358 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
1359 1359 if info.address:
1360 1360 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
1361 1361 self.send(out)
1362 1362 i += 1
1363 1363 nextTime += _REGISTER_TIME
1364 1364
1365 1365 def unregisterService(self, info):
1366 1366 """Unregister a service."""
1367 1367 try:
1368 1368 del(self.services[info.name.lower()])
1369 1369 if self.servicetypes[info.type]>1:
1370 1370 self.servicetypes[info.type]-=1
1371 1371 else:
1372 1372 del self.servicetypes[info.type]
1373 1373 except KeyError:
1374 1374 pass
1375 1375 now = currentTimeMillis()
1376 1376 nextTime = now
1377 1377 i = 0
1378 1378 while i < 3:
1379 1379 if now < nextTime:
1380 1380 self.wait(nextTime - now)
1381 1381 now = currentTimeMillis()
1382 1382 continue
1383 1383 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1384 1384 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1385 1385 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
1386 1386 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1387 1387 if info.address:
1388 1388 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1389 1389 self.send(out)
1390 1390 i += 1
1391 1391 nextTime += _UNREGISTER_TIME
1392 1392
1393 1393 def unregisterAllServices(self):
1394 1394 """Unregister all registered services."""
1395 1395 if len(self.services) > 0:
1396 1396 now = currentTimeMillis()
1397 1397 nextTime = now
1398 1398 i = 0
1399 1399 while i < 3:
1400 1400 if now < nextTime:
1401 1401 self.wait(nextTime - now)
1402 1402 now = currentTimeMillis()
1403 1403 continue
1404 1404 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1405 1405 for info in self.services.values():
1406 1406 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1407 1407 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
1408 1408 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1409 1409 if info.address:
1410 1410 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1411 1411 self.send(out)
1412 1412 i += 1
1413 1413 nextTime += _UNREGISTER_TIME
1414 1414
1415 1415 def checkService(self, info):
1416 1416 """Checks the network for a unique service name, modifying the
1417 1417 ServiceInfo passed in if it is not unique."""
1418 1418 now = currentTimeMillis()
1419 1419 nextTime = now
1420 1420 i = 0
1421 1421 while i < 3:
1422 1422 for record in self.cache.entriesWithName(info.type):
1423 1423 if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
1424 1424 if (info.name.find('.') < 0):
1425 1425 info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
1426 1426 self.checkService(info)
1427 1427 return
1428 1428 raise NonUniqueNameException
1429 1429 if now < nextTime:
1430 1430 self.wait(nextTime - now)
1431 1431 now = currentTimeMillis()
1432 1432 continue
1433 1433 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1434 1434 self.debug = out
1435 1435 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1436 out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
1436 out.addAuthoritativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
1437 1437 self.send(out)
1438 1438 i += 1
1439 1439 nextTime += _CHECK_TIME
1440 1440
1441 1441 def addListener(self, listener, question):
1442 1442 """Adds a listener for a given question. The listener will have
1443 1443 its updateRecord method called when information is available to
1444 1444 answer the question."""
1445 1445 now = currentTimeMillis()
1446 1446 self.listeners.append(listener)
1447 1447 if question is not None:
1448 1448 for record in self.cache.entriesWithName(question.name):
1449 1449 if question.answeredBy(record) and not record.isExpired(now):
1450 1450 listener.updateRecord(self, now, record)
1451 1451 self.notifyAll()
1452 1452
1453 1453 def removeListener(self, listener):
1454 1454 """Removes a listener."""
1455 1455 try:
1456 1456 self.listeners.remove(listener)
1457 1457 self.notifyAll()
1458 1458 except Exception:
1459 1459 pass
1460 1460
1461 1461 def updateRecord(self, now, rec):
1462 1462 """Used to notify listeners of new information that has updated
1463 1463 a record."""
1464 1464 for listener in self.listeners:
1465 1465 listener.updateRecord(self, now, rec)
1466 1466 self.notifyAll()
1467 1467
1468 1468 def handleResponse(self, msg):
1469 1469 """Deal with incoming response packets. All answers
1470 1470 are held in the cache, and listeners are notified."""
1471 1471 now = currentTimeMillis()
1472 1472 for record in msg.answers:
1473 1473 expired = record.isExpired(now)
1474 1474 if record in self.cache.entries():
1475 1475 if expired:
1476 1476 self.cache.remove(record)
1477 1477 else:
1478 1478 entry = self.cache.get(record)
1479 1479 if entry is not None:
1480 1480 entry.resetTTL(record)
1481 1481 record = entry
1482 1482 else:
1483 1483 self.cache.add(record)
1484 1484
1485 1485 self.updateRecord(now, record)
1486 1486
1487 1487 def handleQuery(self, msg, addr, port):
1488 1488 """Deal with incoming query packets. Provides a response if
1489 1489 possible."""
1490 1490 out = None
1491 1491
1492 1492 # Support unicast client responses
1493 1493 #
1494 1494 if port != _MDNS_PORT:
1495 1495 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1496 1496 for question in msg.questions:
1497 1497 out.addQuestion(question)
1498 1498
1499 1499 for question in msg.questions:
1500 1500 if question.type == _TYPE_PTR:
1501 1501 if question.name == "_services._dns-sd._udp.local.":
1502 1502 for stype in self.servicetypes.keys():
1503 1503 if out is None:
1504 1504 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1505 1505 out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype))
1506 1506 for service in self.services.values():
1507 1507 if question.name == service.type:
1508 1508 if out is None:
1509 1509 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1510 1510 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
1511 1511 else:
1512 1512 try:
1513 1513 if out is None:
1514 1514 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1515 1515
1516 1516 # Answer A record queries for any service addresses we know
1517 1517 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1518 1518 for service in self.services.values():
1519 1519 if service.server == question.name.lower():
1520 1520 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1521 1521
1522 1522 service = self.services.get(question.name.lower(), None)
1523 1523 if not service: continue
1524 1524
1525 1525 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1526 1526 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
1527 1527 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1528 1528 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
1529 1529 if question.type == _TYPE_SRV:
1530 1530 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1531 1531 except Exception:
1532 1532 traceback.print_exc()
1533 1533
1534 1534 if out is not None and out.answers:
1535 1535 out.id = msg.id
1536 1536 self.send(out, addr, port)
1537 1537
1538 1538 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
1539 1539 """Sends an outgoing packet."""
1540 1540 # This is a quick test to see if we can parse the packets we generate
1541 1541 #temp = DNSIncoming(out.packet())
1542 1542 try:
1543 1543 self.socket.sendto(out.packet(), 0, (addr, port))
1544 1544 except Exception:
1545 1545 # Ignore this, it may be a temporary loss of network connection
1546 1546 pass
1547 1547
1548 1548 def close(self):
1549 1549 """Ends the background threads, and prevent this instance from
1550 1550 servicing further queries."""
1551 1551 if globals()['_GLOBAL_DONE'] == 0:
1552 1552 globals()['_GLOBAL_DONE'] = 1
1553 1553 self.notifyAll()
1554 1554 self.engine.notify()
1555 1555 self.unregisterAllServices()
1556 1556 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1557 1557 self.socket.close()
1558 1558
1559 1559 # Test a few module features, including service registration, service
1560 1560 # query (for Zoe), and service unregistration.
1561 1561
1562 1562 if __name__ == '__main__':
1563 1563 print "Multicast DNS Service Discovery for Python, version", __version__
1564 1564 r = Zeroconf()
1565 1565 print "1. Testing registration of a service..."
1566 1566 desc = {'version':'0.10','a':'test value', 'b':'another value'}
1567 1567 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
1568 1568 print " Registering service..."
1569 1569 r.registerService(info)
1570 1570 print " Registration done."
1571 1571 print "2. Testing query of service information..."
1572 1572 print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
1573 1573 print " Query done."
1574 1574 print "3. Testing query of own service..."
1575 1575 print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
1576 1576 print " Query done."
1577 1577 print "4. Testing unregister of service information..."
1578 1578 r.unregisterService(info)
1579 1579 print " Unregister done."
1580 1580 r.close()
1581 1581
1582 1582 # no-check-code
@@ -1,254 +1,254
1 1 # Mercurial bookmark support code
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
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 mercurial.i18n import _
9 9 from mercurial.node import hex
10 10 from mercurial import encoding, error, util
11 11 import errno, os
12 12
13 13 def valid(mark):
14 14 for c in (':', '\0', '\n', '\r'):
15 15 if c in mark:
16 16 return False
17 17 return True
18 18
19 19 def read(repo):
20 20 '''Parse .hg/bookmarks file and return a dictionary
21 21
22 22 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
23 23 in the .hg/bookmarks file.
24 24 Read the file and return a (name=>nodeid) dictionary
25 25 '''
26 26 bookmarks = {}
27 27 try:
28 28 for line in repo.opener('bookmarks'):
29 29 line = line.strip()
30 30 if not line:
31 31 continue
32 32 if ' ' not in line:
33 33 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line)
34 34 continue
35 35 sha, refspec = line.split(' ', 1)
36 36 refspec = encoding.tolocal(refspec)
37 37 try:
38 38 bookmarks[refspec] = repo.changelog.lookup(sha)
39 39 except LookupError:
40 40 pass
41 41 except IOError, inst:
42 42 if inst.errno != errno.ENOENT:
43 43 raise
44 44 return bookmarks
45 45
46 46 def readcurrent(repo):
47 47 '''Get the current bookmark
48 48
49 49 If we use gittishsh branches we have a current bookmark that
50 50 we are on. This function returns the name of the bookmark. It
51 51 is stored in .hg/bookmarks.current
52 52 '''
53 53 mark = None
54 54 try:
55 55 file = repo.opener('bookmarks.current')
56 56 except IOError, inst:
57 57 if inst.errno != errno.ENOENT:
58 58 raise
59 59 return None
60 60 try:
61 # No readline() in posixfile_nt, reading everything is cheap
61 # No readline() in osutil.posixfile, reading everything is cheap
62 62 mark = encoding.tolocal((file.readlines() or [''])[0])
63 63 if mark == '' or mark not in repo._bookmarks:
64 64 mark = None
65 65 finally:
66 66 file.close()
67 67 return mark
68 68
69 69 def write(repo):
70 70 '''Write bookmarks
71 71
72 72 Write the given bookmark => hash dictionary to the .hg/bookmarks file
73 73 in a format equal to those of localtags.
74 74
75 75 We also store a backup of the previous state in undo.bookmarks that
76 76 can be copied back on rollback.
77 77 '''
78 78 refs = repo._bookmarks
79 79
80 80 if repo._bookmarkcurrent not in refs:
81 81 setcurrent(repo, None)
82 82 for mark in refs.keys():
83 83 if not valid(mark):
84 84 raise util.Abort(_("bookmark '%s' contains illegal "
85 85 "character" % mark))
86 86
87 87 wlock = repo.wlock()
88 88 try:
89 89
90 90 file = repo.opener('bookmarks', 'w', atomictemp=True)
91 91 for refspec, node in refs.iteritems():
92 92 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
93 93 file.close()
94 94
95 95 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
96 96 try:
97 97 os.utime(repo.sjoin('00changelog.i'), None)
98 98 except OSError:
99 99 pass
100 100
101 101 finally:
102 102 wlock.release()
103 103
104 104 def setcurrent(repo, mark):
105 105 '''Set the name of the bookmark that we are currently on
106 106
107 107 Set the name of the bookmark that we are on (hg update <bookmark>).
108 108 The name is recorded in .hg/bookmarks.current
109 109 '''
110 110 current = repo._bookmarkcurrent
111 111 if current == mark:
112 112 return
113 113
114 114 if mark not in repo._bookmarks:
115 115 mark = ''
116 116 if not valid(mark):
117 117 raise util.Abort(_("bookmark '%s' contains illegal "
118 118 "character" % mark))
119 119
120 120 wlock = repo.wlock()
121 121 try:
122 122 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
123 123 file.write(encoding.fromlocal(mark))
124 124 file.close()
125 125 finally:
126 126 wlock.release()
127 127 repo._bookmarkcurrent = mark
128 128
129 129 def unsetcurrent(repo):
130 130 wlock = repo.wlock()
131 131 try:
132 132 try:
133 133 util.unlink(repo.join('bookmarks.current'))
134 134 repo._bookmarkcurrent = None
135 135 except OSError, inst:
136 136 if inst.errno != errno.ENOENT:
137 137 raise
138 138 finally:
139 139 wlock.release()
140 140
141 141 def updatecurrentbookmark(repo, oldnode, curbranch):
142 142 try:
143 143 return update(repo, oldnode, repo.branchtip(curbranch))
144 144 except error.RepoLookupError:
145 145 if curbranch == "default": # no default branch!
146 146 return update(repo, oldnode, repo.lookup("tip"))
147 147 else:
148 148 raise util.Abort(_("branch %s not found") % curbranch)
149 149
150 150 def update(repo, parents, node):
151 151 marks = repo._bookmarks
152 152 update = False
153 153 cur = repo._bookmarkcurrent
154 154 if not cur:
155 155 return False
156 156
157 157 toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]]
158 158 for mark in toupdate:
159 159 if mark and marks[mark] in parents:
160 160 old = repo[marks[mark]]
161 161 new = repo[node]
162 162 if new in old.descendants() and mark == cur:
163 163 marks[cur] = new.node()
164 164 update = True
165 165 if mark != cur:
166 166 del marks[mark]
167 167 if update:
168 168 repo._writebookmarks(marks)
169 169 return update
170 170
171 171 def listbookmarks(repo):
172 172 # We may try to list bookmarks on a repo type that does not
173 173 # support it (e.g., statichttprepository).
174 174 marks = getattr(repo, '_bookmarks', {})
175 175
176 176 d = {}
177 177 for k, v in marks.iteritems():
178 178 # don't expose local divergent bookmarks
179 179 if '@' not in k or k.endswith('@'):
180 180 d[k] = hex(v)
181 181 return d
182 182
183 183 def pushbookmark(repo, key, old, new):
184 184 w = repo.wlock()
185 185 try:
186 186 marks = repo._bookmarks
187 187 if hex(marks.get(key, '')) != old:
188 188 return False
189 189 if new == '':
190 190 del marks[key]
191 191 else:
192 192 if new not in repo:
193 193 return False
194 194 marks[key] = repo[new].node()
195 195 write(repo)
196 196 return True
197 197 finally:
198 198 w.release()
199 199
200 200 def updatefromremote(ui, repo, remote, path):
201 201 ui.debug("checking for updated bookmarks\n")
202 202 rb = remote.listkeys('bookmarks')
203 203 changed = False
204 204 for k in rb.keys():
205 205 if k in repo._bookmarks:
206 206 nr, nl = rb[k], repo._bookmarks[k]
207 207 if nr in repo:
208 208 cr = repo[nr]
209 209 cl = repo[nl]
210 210 if cl.rev() >= cr.rev():
211 211 continue
212 212 if cr in cl.descendants():
213 213 repo._bookmarks[k] = cr.node()
214 214 changed = True
215 215 ui.status(_("updating bookmark %s\n") % k)
216 216 else:
217 217 # find a unique @ suffix
218 218 for x in range(1, 100):
219 219 n = '%s@%d' % (k, x)
220 220 if n not in repo._bookmarks:
221 221 break
222 222 # try to use an @pathalias suffix
223 223 # if an @pathalias already exists, we overwrite (update) it
224 224 for p, u in ui.configitems("paths"):
225 225 if path == u:
226 226 n = '%s@%s' % (k, p)
227 227
228 228 repo._bookmarks[n] = cr.node()
229 229 changed = True
230 230 ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
231 231 elif rb[k] in repo:
232 232 # add remote bookmarks for changes we already have
233 233 repo._bookmarks[k] = repo[rb[k]].node()
234 234 changed = True
235 235 ui.status(_("adding remote bookmark %s\n") % k)
236 236
237 237 if changed:
238 238 write(repo)
239 239
240 240 def diff(ui, repo, remote):
241 241 ui.status(_("searching for changed bookmarks\n"))
242 242
243 243 lmarks = repo.listkeys('bookmarks')
244 244 rmarks = remote.listkeys('bookmarks')
245 245
246 246 diff = sorted(set(rmarks) - set(lmarks))
247 247 for k in diff:
248 248 mark = ui.debugflag and rmarks[k] or rmarks[k][:12]
249 249 ui.write(" %-25s %s\n" % (k, mark))
250 250
251 251 if len(diff) <= 0:
252 252 ui.status(_("no changed bookmarks found\n"))
253 253 return 1
254 254 return 0
@@ -1,5902 +1,5902
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, graphmod
19 19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
20 20 import phases, obsolete
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 ('G', 'graph', None, _("show the revision DAG")),
102 102 ] + templateopts
103 103
104 104 diffopts = [
105 105 ('a', 'text', None, _('treat all files as text')),
106 106 ('g', 'git', None, _('use git extended diff format')),
107 107 ('', 'nodates', None, _('omit dates from diff headers'))
108 108 ]
109 109
110 110 diffwsopts = [
111 111 ('w', 'ignore-all-space', None,
112 112 _('ignore white space when comparing lines')),
113 113 ('b', 'ignore-space-change', None,
114 114 _('ignore changes in the amount of white space')),
115 115 ('B', 'ignore-blank-lines', None,
116 116 _('ignore changes whose lines are all blank')),
117 117 ]
118 118
119 119 diffopts2 = [
120 120 ('p', 'show-function', None, _('show which function each change is in')),
121 121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
122 122 ] + diffwsopts + [
123 123 ('U', 'unified', '',
124 124 _('number of lines of context to show'), _('NUM')),
125 125 ('', 'stat', None, _('output diffstat-style summary of changes')),
126 126 ]
127 127
128 128 mergetoolopts = [
129 129 ('t', 'tool', '', _('specify merge tool')),
130 130 ]
131 131
132 132 similarityopts = [
133 133 ('s', 'similarity', '',
134 134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
135 135 ]
136 136
137 137 subrepoopts = [
138 138 ('S', 'subrepos', None,
139 139 _('recurse into subrepositories'))
140 140 ]
141 141
142 142 # Commands start here, listed alphabetically
143 143
144 144 @command('^add',
145 145 walkopts + subrepoopts + dryrunopts,
146 146 _('[OPTION]... [FILE]...'))
147 147 def add(ui, repo, *pats, **opts):
148 148 """add the specified files on the next commit
149 149
150 150 Schedule files to be version controlled and added to the
151 151 repository.
152 152
153 153 The files will be added to the repository at the next commit. To
154 154 undo an add before that, see :hg:`forget`.
155 155
156 156 If no names are given, add all files to the repository.
157 157
158 158 .. container:: verbose
159 159
160 160 An example showing how new (unknown) files are added
161 161 automatically by :hg:`add`::
162 162
163 163 $ ls
164 164 foo.c
165 165 $ hg status
166 166 ? foo.c
167 167 $ hg add
168 168 adding foo.c
169 169 $ hg status
170 170 A foo.c
171 171
172 172 Returns 0 if all files are successfully added.
173 173 """
174 174
175 175 m = scmutil.match(repo[None], pats, opts)
176 176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
177 177 opts.get('subrepos'), prefix="", explicitonly=False)
178 178 return rejected and 1 or 0
179 179
180 180 @command('addremove',
181 181 similarityopts + walkopts + dryrunopts,
182 182 _('[OPTION]... [FILE]...'))
183 183 def addremove(ui, repo, *pats, **opts):
184 184 """add all new files, delete all missing files
185 185
186 186 Add all new files and remove all missing files from the
187 187 repository.
188 188
189 189 New files are ignored if they match any of the patterns in
190 190 ``.hgignore``. As with add, these changes take effect at the next
191 191 commit.
192 192
193 193 Use the -s/--similarity option to detect renamed files. This
194 194 option takes a percentage between 0 (disabled) and 100 (files must
195 195 be identical) as its parameter. With a parameter greater than 0,
196 196 this compares every removed file with every added file and records
197 197 those similar enough as renames. Detecting renamed files this way
198 198 can be expensive. After using this option, :hg:`status -C` can be
199 199 used to check which files were identified as moved or renamed. If
200 200 not specified, -s/--similarity defaults to 100 and only renames of
201 201 identical files are detected.
202 202
203 203 Returns 0 if all files are successfully added.
204 204 """
205 205 try:
206 206 sim = float(opts.get('similarity') or 100)
207 207 except ValueError:
208 208 raise util.Abort(_('similarity must be a number'))
209 209 if sim < 0 or sim > 100:
210 210 raise util.Abort(_('similarity must be between 0 and 100'))
211 211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
212 212
213 213 @command('^annotate|blame',
214 214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
215 215 ('', 'follow', None,
216 216 _('follow copies/renames and list the filename (DEPRECATED)')),
217 217 ('', 'no-follow', None, _("don't follow copies and renames")),
218 218 ('a', 'text', None, _('treat all files as text')),
219 219 ('u', 'user', None, _('list the author (long with -v)')),
220 220 ('f', 'file', None, _('list the filename')),
221 221 ('d', 'date', None, _('list the date (short with -q)')),
222 222 ('n', 'number', None, _('list the revision number (default)')),
223 223 ('c', 'changeset', None, _('list the changeset')),
224 224 ('l', 'line-number', None, _('show line number at the first appearance'))
225 225 ] + diffwsopts + walkopts,
226 226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
227 227 def annotate(ui, repo, *pats, **opts):
228 228 """show changeset information by line for each file
229 229
230 230 List changes in files, showing the revision id responsible for
231 231 each line
232 232
233 233 This command is useful for discovering when a change was made and
234 234 by whom.
235 235
236 236 Without the -a/--text option, annotate will avoid processing files
237 237 it detects as binary. With -a, annotate will annotate the file
238 238 anyway, although the results will probably be neither useful
239 239 nor desirable.
240 240
241 241 Returns 0 on success.
242 242 """
243 243 if opts.get('follow'):
244 244 # --follow is deprecated and now just an alias for -f/--file
245 245 # to mimic the behavior of Mercurial before version 1.5
246 246 opts['file'] = True
247 247
248 248 datefunc = ui.quiet and util.shortdate or util.datestr
249 249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
250 250
251 251 if not pats:
252 252 raise util.Abort(_('at least one filename or pattern is required'))
253 253
254 254 hexfn = ui.debugflag and hex or short
255 255
256 256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
257 257 ('number', ' ', lambda x: str(x[0].rev())),
258 258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
259 259 ('date', ' ', getdate),
260 260 ('file', ' ', lambda x: x[0].path()),
261 261 ('line_number', ':', lambda x: str(x[1])),
262 262 ]
263 263
264 264 if (not opts.get('user') and not opts.get('changeset')
265 265 and not opts.get('date') and not opts.get('file')):
266 266 opts['number'] = True
267 267
268 268 linenumber = opts.get('line_number') is not None
269 269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
270 270 raise util.Abort(_('at least one of -n/-c is required for -l'))
271 271
272 272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
273 273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
274 274
275 275 def bad(x, y):
276 276 raise util.Abort("%s: %s" % (x, y))
277 277
278 278 ctx = scmutil.revsingle(repo, opts.get('rev'))
279 279 m = scmutil.match(ctx, pats, opts)
280 280 m.bad = bad
281 281 follow = not opts.get('no_follow')
282 282 diffopts = patch.diffopts(ui, opts, section='annotate')
283 283 for abs in ctx.walk(m):
284 284 fctx = ctx[abs]
285 285 if not opts.get('text') and util.binary(fctx.data()):
286 286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
287 287 continue
288 288
289 289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
290 290 diffopts=diffopts)
291 291 pieces = []
292 292
293 293 for f, sep in funcmap:
294 294 l = [f(n) for n, dummy in lines]
295 295 if l:
296 296 sized = [(x, encoding.colwidth(x)) for x in l]
297 297 ml = max([w for x, w in sized])
298 298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
299 299 for x, w in sized])
300 300
301 301 if pieces:
302 302 for p, l in zip(zip(*pieces), lines):
303 303 ui.write("%s: %s" % ("".join(p), l[1]))
304 304
305 305 if lines and not lines[-1][1].endswith('\n'):
306 306 ui.write('\n')
307 307
308 308 @command('archive',
309 309 [('', 'no-decode', None, _('do not pass files through decoders')),
310 310 ('p', 'prefix', '', _('directory prefix for files in archive'),
311 311 _('PREFIX')),
312 312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
313 313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
314 314 ] + subrepoopts + walkopts,
315 315 _('[OPTION]... DEST'))
316 316 def archive(ui, repo, dest, **opts):
317 317 '''create an unversioned archive of a repository revision
318 318
319 319 By default, the revision used is the parent of the working
320 320 directory; use -r/--rev to specify a different revision.
321 321
322 322 The archive type is automatically detected based on file
323 323 extension (or override using -t/--type).
324 324
325 325 .. container:: verbose
326 326
327 327 Examples:
328 328
329 329 - create a zip file containing the 1.0 release::
330 330
331 331 hg archive -r 1.0 project-1.0.zip
332 332
333 333 - create a tarball excluding .hg files::
334 334
335 335 hg archive project.tar.gz -X ".hg*"
336 336
337 337 Valid types are:
338 338
339 339 :``files``: a directory full of files (default)
340 340 :``tar``: tar archive, uncompressed
341 341 :``tbz2``: tar archive, compressed using bzip2
342 342 :``tgz``: tar archive, compressed using gzip
343 343 :``uzip``: zip archive, uncompressed
344 344 :``zip``: zip archive, compressed using deflate
345 345
346 346 The exact name of the destination archive or directory is given
347 347 using a format string; see :hg:`help export` for details.
348 348
349 349 Each member added to an archive file has a directory prefix
350 350 prepended. Use -p/--prefix to specify a format string for the
351 351 prefix. The default is the basename of the archive, with suffixes
352 352 removed.
353 353
354 354 Returns 0 on success.
355 355 '''
356 356
357 357 ctx = scmutil.revsingle(repo, opts.get('rev'))
358 358 if not ctx:
359 359 raise util.Abort(_('no working directory: please specify a revision'))
360 360 node = ctx.node()
361 361 dest = cmdutil.makefilename(repo, dest, node)
362 362 if os.path.realpath(dest) == repo.root:
363 363 raise util.Abort(_('repository root cannot be destination'))
364 364
365 365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
366 366 prefix = opts.get('prefix')
367 367
368 368 if dest == '-':
369 369 if kind == 'files':
370 370 raise util.Abort(_('cannot archive plain files to stdout'))
371 371 dest = cmdutil.makefileobj(repo, dest)
372 372 if not prefix:
373 373 prefix = os.path.basename(repo.root) + '-%h'
374 374
375 375 prefix = cmdutil.makefilename(repo, prefix, node)
376 376 matchfn = scmutil.match(ctx, [], opts)
377 377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
378 378 matchfn, prefix, subrepos=opts.get('subrepos'))
379 379
380 380 @command('backout',
381 381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
382 382 ('', 'parent', '',
383 383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
384 384 ('r', 'rev', '', _('revision to backout'), _('REV')),
385 385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
386 386 _('[OPTION]... [-r] REV'))
387 387 def backout(ui, repo, node=None, rev=None, **opts):
388 388 '''reverse effect of earlier changeset
389 389
390 390 Prepare a new changeset with the effect of REV undone in the
391 391 current working directory.
392 392
393 393 If REV is the parent of the working directory, then this new changeset
394 394 is committed automatically. Otherwise, hg needs to merge the
395 395 changes and the merged result is left uncommitted.
396 396
397 397 .. note::
398 398 backout cannot be used to fix either an unwanted or
399 399 incorrect merge.
400 400
401 401 .. container:: verbose
402 402
403 403 By default, the pending changeset will have one parent,
404 404 maintaining a linear history. With --merge, the pending
405 405 changeset will instead have two parents: the old parent of the
406 406 working directory and a new child of REV that simply undoes REV.
407 407
408 408 Before version 1.7, the behavior without --merge was equivalent
409 409 to specifying --merge followed by :hg:`update --clean .` to
410 410 cancel the merge and leave the child of REV as a head to be
411 411 merged separately.
412 412
413 413 See :hg:`help dates` for a list of formats valid for -d/--date.
414 414
415 415 Returns 0 on success.
416 416 '''
417 417 if rev and node:
418 418 raise util.Abort(_("please specify just one revision"))
419 419
420 420 if not rev:
421 421 rev = node
422 422
423 423 if not rev:
424 424 raise util.Abort(_("please specify a revision to backout"))
425 425
426 426 date = opts.get('date')
427 427 if date:
428 428 opts['date'] = util.parsedate(date)
429 429
430 430 cmdutil.bailifchanged(repo)
431 431 node = scmutil.revsingle(repo, rev).node()
432 432
433 433 op1, op2 = repo.dirstate.parents()
434 434 a = repo.changelog.ancestor(op1, node)
435 435 if a != node:
436 436 raise util.Abort(_('cannot backout change on a different branch'))
437 437
438 438 p1, p2 = repo.changelog.parents(node)
439 439 if p1 == nullid:
440 440 raise util.Abort(_('cannot backout a change with no parents'))
441 441 if p2 != nullid:
442 442 if not opts.get('parent'):
443 443 raise util.Abort(_('cannot backout a merge changeset'))
444 444 p = repo.lookup(opts['parent'])
445 445 if p not in (p1, p2):
446 446 raise util.Abort(_('%s is not a parent of %s') %
447 447 (short(p), short(node)))
448 448 parent = p
449 449 else:
450 450 if opts.get('parent'):
451 451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
452 452 parent = p1
453 453
454 454 # the backout should appear on the same branch
455 455 wlock = repo.wlock()
456 456 try:
457 457 branch = repo.dirstate.branch()
458 458 hg.clean(repo, node, show_stats=False)
459 459 repo.dirstate.setbranch(branch)
460 460 revert_opts = opts.copy()
461 461 revert_opts['date'] = None
462 462 revert_opts['all'] = True
463 463 revert_opts['rev'] = hex(parent)
464 464 revert_opts['no_backup'] = None
465 465 revert(ui, repo, **revert_opts)
466 466 if not opts.get('merge') and op1 != node:
467 467 try:
468 468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
469 469 return hg.update(repo, op1)
470 470 finally:
471 471 ui.setconfig('ui', 'forcemerge', '')
472 472
473 473 commit_opts = opts.copy()
474 474 commit_opts['addremove'] = False
475 475 if not commit_opts['message'] and not commit_opts['logfile']:
476 476 # we don't translate commit messages
477 477 commit_opts['message'] = "Backed out changeset %s" % short(node)
478 478 commit_opts['force_editor'] = True
479 479 commit(ui, repo, **commit_opts)
480 480 def nice(node):
481 481 return '%d:%s' % (repo.changelog.rev(node), short(node))
482 482 ui.status(_('changeset %s backs out changeset %s\n') %
483 483 (nice(repo.changelog.tip()), nice(node)))
484 484 if opts.get('merge') and op1 != node:
485 485 hg.clean(repo, op1, show_stats=False)
486 486 ui.status(_('merging with changeset %s\n')
487 487 % nice(repo.changelog.tip()))
488 488 try:
489 489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
490 490 return hg.merge(repo, hex(repo.changelog.tip()))
491 491 finally:
492 492 ui.setconfig('ui', 'forcemerge', '')
493 493 finally:
494 494 wlock.release()
495 495 return 0
496 496
497 497 @command('bisect',
498 498 [('r', 'reset', False, _('reset bisect state')),
499 499 ('g', 'good', False, _('mark changeset good')),
500 500 ('b', 'bad', False, _('mark changeset bad')),
501 501 ('s', 'skip', False, _('skip testing changeset')),
502 502 ('e', 'extend', False, _('extend the bisect range')),
503 503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
504 504 ('U', 'noupdate', False, _('do not update to target'))],
505 505 _("[-gbsr] [-U] [-c CMD] [REV]"))
506 506 def bisect(ui, repo, rev=None, extra=None, command=None,
507 507 reset=None, good=None, bad=None, skip=None, extend=None,
508 508 noupdate=None):
509 509 """subdivision search of changesets
510 510
511 511 This command helps to find changesets which introduce problems. To
512 512 use, mark the earliest changeset you know exhibits the problem as
513 513 bad, then mark the latest changeset which is free from the problem
514 514 as good. Bisect will update your working directory to a revision
515 515 for testing (unless the -U/--noupdate option is specified). Once
516 516 you have performed tests, mark the working directory as good or
517 517 bad, and bisect will either update to another candidate changeset
518 518 or announce that it has found the bad revision.
519 519
520 520 As a shortcut, you can also use the revision argument to mark a
521 521 revision as good or bad without checking it out first.
522 522
523 523 If you supply a command, it will be used for automatic bisection.
524 524 The environment variable HG_NODE will contain the ID of the
525 525 changeset being tested. The exit status of the command will be
526 526 used to mark revisions as good or bad: status 0 means good, 125
527 527 means to skip the revision, 127 (command not found) will abort the
528 528 bisection, and any other non-zero exit status means the revision
529 529 is bad.
530 530
531 531 .. container:: verbose
532 532
533 533 Some examples:
534 534
535 535 - start a bisection with known bad revision 12, and good revision 34::
536 536
537 537 hg bisect --bad 34
538 538 hg bisect --good 12
539 539
540 540 - advance the current bisection by marking current revision as good or
541 541 bad::
542 542
543 543 hg bisect --good
544 544 hg bisect --bad
545 545
546 546 - mark the current revision, or a known revision, to be skipped (e.g. if
547 547 that revision is not usable because of another issue)::
548 548
549 549 hg bisect --skip
550 550 hg bisect --skip 23
551 551
552 552 - forget the current bisection::
553 553
554 554 hg bisect --reset
555 555
556 556 - use 'make && make tests' to automatically find the first broken
557 557 revision::
558 558
559 559 hg bisect --reset
560 560 hg bisect --bad 34
561 561 hg bisect --good 12
562 562 hg bisect --command 'make && make tests'
563 563
564 564 - see all changesets whose states are already known in the current
565 565 bisection::
566 566
567 567 hg log -r "bisect(pruned)"
568 568
569 569 - see the changeset currently being bisected (especially useful
570 570 if running with -U/--noupdate)::
571 571
572 572 hg log -r "bisect(current)"
573 573
574 574 - see all changesets that took part in the current bisection::
575 575
576 576 hg log -r "bisect(range)"
577 577
578 578 - with the graphlog extension, you can even get a nice graph::
579 579
580 580 hg log --graph -r "bisect(range)"
581 581
582 582 See :hg:`help revsets` for more about the `bisect()` keyword.
583 583
584 584 Returns 0 on success.
585 585 """
586 586 def extendbisectrange(nodes, good):
587 587 # bisect is incomplete when it ends on a merge node and
588 588 # one of the parent was not checked.
589 589 parents = repo[nodes[0]].parents()
590 590 if len(parents) > 1:
591 591 side = good and state['bad'] or state['good']
592 592 num = len(set(i.node() for i in parents) & set(side))
593 593 if num == 1:
594 594 return parents[0].ancestor(parents[1])
595 595 return None
596 596
597 597 def print_result(nodes, good):
598 598 displayer = cmdutil.show_changeset(ui, repo, {})
599 599 if len(nodes) == 1:
600 600 # narrowed it down to a single revision
601 601 if good:
602 602 ui.write(_("The first good revision is:\n"))
603 603 else:
604 604 ui.write(_("The first bad revision is:\n"))
605 605 displayer.show(repo[nodes[0]])
606 606 extendnode = extendbisectrange(nodes, good)
607 607 if extendnode is not None:
608 608 ui.write(_('Not all ancestors of this changeset have been'
609 609 ' checked.\nUse bisect --extend to continue the '
610 610 'bisection from\nthe common ancestor, %s.\n')
611 611 % extendnode)
612 612 else:
613 613 # multiple possible revisions
614 614 if good:
615 615 ui.write(_("Due to skipped revisions, the first "
616 616 "good revision could be any of:\n"))
617 617 else:
618 618 ui.write(_("Due to skipped revisions, the first "
619 619 "bad revision could be any of:\n"))
620 620 for n in nodes:
621 621 displayer.show(repo[n])
622 622 displayer.close()
623 623
624 624 def check_state(state, interactive=True):
625 625 if not state['good'] or not state['bad']:
626 626 if (good or bad or skip or reset) and interactive:
627 627 return
628 628 if not state['good']:
629 629 raise util.Abort(_('cannot bisect (no known good revisions)'))
630 630 else:
631 631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
632 632 return True
633 633
634 634 # backward compatibility
635 635 if rev in "good bad reset init".split():
636 636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
637 637 cmd, rev, extra = rev, extra, None
638 638 if cmd == "good":
639 639 good = True
640 640 elif cmd == "bad":
641 641 bad = True
642 642 else:
643 643 reset = True
644 644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
645 645 raise util.Abort(_('incompatible arguments'))
646 646
647 647 if reset:
648 648 p = repo.join("bisect.state")
649 649 if os.path.exists(p):
650 650 os.unlink(p)
651 651 return
652 652
653 653 state = hbisect.load_state(repo)
654 654
655 655 if command:
656 656 changesets = 1
657 657 try:
658 658 node = state['current'][0]
659 659 except LookupError:
660 660 if noupdate:
661 661 raise util.Abort(_('current bisect revision is unknown - '
662 662 'start a new bisect to fix'))
663 663 node, p2 = repo.dirstate.parents()
664 664 if p2 != nullid:
665 665 raise util.Abort(_('current bisect revision is a merge'))
666 666 try:
667 667 while changesets:
668 668 # update state
669 669 state['current'] = [node]
670 670 hbisect.save_state(repo, state)
671 671 status = util.system(command,
672 672 environ={'HG_NODE': hex(node)},
673 673 out=ui.fout)
674 674 if status == 125:
675 675 transition = "skip"
676 676 elif status == 0:
677 677 transition = "good"
678 678 # status < 0 means process was killed
679 679 elif status == 127:
680 680 raise util.Abort(_("failed to execute %s") % command)
681 681 elif status < 0:
682 682 raise util.Abort(_("%s killed") % command)
683 683 else:
684 684 transition = "bad"
685 685 ctx = scmutil.revsingle(repo, rev, node)
686 686 rev = None # clear for future iterations
687 687 state[transition].append(ctx.node())
688 688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
689 689 check_state(state, interactive=False)
690 690 # bisect
691 691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
692 692 # update to next check
693 693 node = nodes[0]
694 694 if not noupdate:
695 695 cmdutil.bailifchanged(repo)
696 696 hg.clean(repo, node, show_stats=False)
697 697 finally:
698 698 state['current'] = [node]
699 699 hbisect.save_state(repo, state)
700 700 print_result(nodes, good)
701 701 return
702 702
703 703 # update state
704 704
705 705 if rev:
706 706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
707 707 else:
708 708 nodes = [repo.lookup('.')]
709 709
710 710 if good or bad or skip:
711 711 if good:
712 712 state['good'] += nodes
713 713 elif bad:
714 714 state['bad'] += nodes
715 715 elif skip:
716 716 state['skip'] += nodes
717 717 hbisect.save_state(repo, state)
718 718
719 719 if not check_state(state):
720 720 return
721 721
722 722 # actually bisect
723 723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
724 724 if extend:
725 725 if not changesets:
726 726 extendnode = extendbisectrange(nodes, good)
727 727 if extendnode is not None:
728 728 ui.write(_("Extending search to changeset %d:%s\n"
729 729 % (extendnode.rev(), extendnode)))
730 730 state['current'] = [extendnode.node()]
731 731 hbisect.save_state(repo, state)
732 732 if noupdate:
733 733 return
734 734 cmdutil.bailifchanged(repo)
735 735 return hg.clean(repo, extendnode.node())
736 736 raise util.Abort(_("nothing to extend"))
737 737
738 738 if changesets == 0:
739 739 print_result(nodes, good)
740 740 else:
741 741 assert len(nodes) == 1 # only a single node can be tested next
742 742 node = nodes[0]
743 743 # compute the approximate number of remaining tests
744 744 tests, size = 0, 2
745 745 while size <= changesets:
746 746 tests, size = tests + 1, size * 2
747 747 rev = repo.changelog.rev(node)
748 748 ui.write(_("Testing changeset %d:%s "
749 749 "(%d changesets remaining, ~%d tests)\n")
750 750 % (rev, short(node), changesets, tests))
751 751 state['current'] = [node]
752 752 hbisect.save_state(repo, state)
753 753 if not noupdate:
754 754 cmdutil.bailifchanged(repo)
755 755 return hg.clean(repo, node)
756 756
757 757 @command('bookmarks',
758 758 [('f', 'force', False, _('force')),
759 759 ('r', 'rev', '', _('revision'), _('REV')),
760 760 ('d', 'delete', False, _('delete a given bookmark')),
761 761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
762 762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
763 763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
764 764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
765 765 rename=None, inactive=False):
766 766 '''track a line of development with movable markers
767 767
768 768 Bookmarks are pointers to certain commits that move when committing.
769 769 Bookmarks are local. They can be renamed, copied and deleted. It is
770 770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
771 771 :hg:`update NAME` to update to a given bookmark.
772 772
773 773 You can use :hg:`bookmark NAME` to set a bookmark on the working
774 774 directory's parent revision with the given name. If you specify
775 775 a revision using -r REV (where REV may be an existing bookmark),
776 776 the bookmark is assigned to that revision.
777 777
778 778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
779 779 push` and :hg:`help pull`). This requires both the local and remote
780 780 repositories to support bookmarks. For versions prior to 1.8, this means
781 781 the bookmarks extension must be enabled.
782 782
783 783 With -i/--inactive, the new bookmark will not be made the active
784 784 bookmark. If -r/--rev is given, the new bookmark will not be made
785 785 active even if -i/--inactive is not given. If no NAME is given, the
786 786 current active bookmark will be marked inactive.
787 787 '''
788 788 hexfn = ui.debugflag and hex or short
789 789 marks = repo._bookmarks
790 790 cur = repo.changectx('.').node()
791 791
792 792 if delete:
793 793 if mark is None:
794 794 raise util.Abort(_("bookmark name required"))
795 795 if mark not in marks:
796 796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
797 797 if mark == repo._bookmarkcurrent:
798 798 bookmarks.setcurrent(repo, None)
799 799 del marks[mark]
800 800 bookmarks.write(repo)
801 801 return
802 802
803 803 if rename:
804 804 if rename not in marks:
805 805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
806 806 if mark in marks and not force:
807 807 raise util.Abort(_("bookmark '%s' already exists "
808 808 "(use -f to force)") % mark)
809 809 if mark is None:
810 810 raise util.Abort(_("new bookmark name required"))
811 811 marks[mark] = marks[rename]
812 812 if repo._bookmarkcurrent == rename and not inactive:
813 813 bookmarks.setcurrent(repo, mark)
814 814 del marks[rename]
815 815 bookmarks.write(repo)
816 816 return
817 817
818 818 if mark is not None:
819 819 if "\n" in mark:
820 820 raise util.Abort(_("bookmark name cannot contain newlines"))
821 821 mark = mark.strip()
822 822 if not mark:
823 823 raise util.Abort(_("bookmark names cannot consist entirely of "
824 824 "whitespace"))
825 825 if inactive and mark == repo._bookmarkcurrent:
826 826 bookmarks.setcurrent(repo, None)
827 827 return
828 828 if mark in marks and not force:
829 829 raise util.Abort(_("bookmark '%s' already exists "
830 830 "(use -f to force)") % mark)
831 831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
832 832 and not force):
833 833 raise util.Abort(
834 834 _("a bookmark cannot have the name of an existing branch"))
835 835 if rev:
836 836 marks[mark] = repo.lookup(rev)
837 837 else:
838 838 marks[mark] = cur
839 839 if not inactive and cur == marks[mark]:
840 840 bookmarks.setcurrent(repo, mark)
841 841 bookmarks.write(repo)
842 842 return
843 843
844 844 if mark is None:
845 845 if rev:
846 846 raise util.Abort(_("bookmark name required"))
847 847 if len(marks) == 0:
848 848 ui.status(_("no bookmarks set\n"))
849 849 else:
850 850 for bmark, n in sorted(marks.iteritems()):
851 851 current = repo._bookmarkcurrent
852 852 if bmark == current and n == cur:
853 853 prefix, label = '*', 'bookmarks.current'
854 854 else:
855 855 prefix, label = ' ', ''
856 856
857 857 if ui.quiet:
858 858 ui.write("%s\n" % bmark, label=label)
859 859 else:
860 860 ui.write(" %s %-25s %d:%s\n" % (
861 861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
862 862 label=label)
863 863 return
864 864
865 865 @command('branch',
866 866 [('f', 'force', None,
867 867 _('set branch name even if it shadows an existing branch')),
868 868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
869 869 _('[-fC] [NAME]'))
870 870 def branch(ui, repo, label=None, **opts):
871 871 """set or show the current branch name
872 872
873 873 .. note::
874 874 Branch names are permanent and global. Use :hg:`bookmark` to create a
875 875 light-weight bookmark instead. See :hg:`help glossary` for more
876 876 information about named branches and bookmarks.
877 877
878 878 With no argument, show the current branch name. With one argument,
879 879 set the working directory branch name (the branch will not exist
880 880 in the repository until the next commit). Standard practice
881 881 recommends that primary development take place on the 'default'
882 882 branch.
883 883
884 884 Unless -f/--force is specified, branch will not let you set a
885 885 branch name that already exists, even if it's inactive.
886 886
887 887 Use -C/--clean to reset the working directory branch to that of
888 888 the parent of the working directory, negating a previous branch
889 889 change.
890 890
891 891 Use the command :hg:`update` to switch to an existing branch. Use
892 892 :hg:`commit --close-branch` to mark this branch as closed.
893 893
894 894 Returns 0 on success.
895 895 """
896 896 if not opts.get('clean') and not label:
897 897 ui.write("%s\n" % repo.dirstate.branch())
898 898 return
899 899
900 900 wlock = repo.wlock()
901 901 try:
902 902 if opts.get('clean'):
903 903 label = repo[None].p1().branch()
904 904 repo.dirstate.setbranch(label)
905 905 ui.status(_('reset working directory to branch %s\n') % label)
906 906 elif label:
907 907 if not opts.get('force') and label in repo.branchmap():
908 908 if label not in [p.branch() for p in repo.parents()]:
909 909 raise util.Abort(_('a branch of the same name already'
910 910 ' exists'),
911 911 # i18n: "it" refers to an existing branch
912 912 hint=_("use 'hg update' to switch to it"))
913 913 repo.dirstate.setbranch(label)
914 914 ui.status(_('marked working directory as branch %s\n') % label)
915 915 ui.status(_('(branches are permanent and global, '
916 916 'did you want a bookmark?)\n'))
917 917 finally:
918 918 wlock.release()
919 919
920 920 @command('branches',
921 921 [('a', 'active', False, _('show only branches that have unmerged heads')),
922 922 ('c', 'closed', False, _('show normal and closed branches'))],
923 923 _('[-ac]'))
924 924 def branches(ui, repo, active=False, closed=False):
925 925 """list repository named branches
926 926
927 927 List the repository's named branches, indicating which ones are
928 928 inactive. If -c/--closed is specified, also list branches which have
929 929 been marked closed (see :hg:`commit --close-branch`).
930 930
931 931 If -a/--active is specified, only show active branches. A branch
932 932 is considered active if it contains repository heads.
933 933
934 934 Use the command :hg:`update` to switch to an existing branch.
935 935
936 936 Returns 0.
937 937 """
938 938
939 939 hexfunc = ui.debugflag and hex or short
940 940
941 941 activebranches = set([repo[n].branch() for n in repo.heads()])
942 942 branches = []
943 943 for tag, heads in repo.branchmap().iteritems():
944 944 for h in reversed(heads):
945 945 ctx = repo[h]
946 946 isopen = not ctx.closesbranch()
947 947 if isopen:
948 948 tip = ctx
949 949 break
950 950 else:
951 951 tip = repo[heads[-1]]
952 952 isactive = tag in activebranches and isopen
953 953 branches.append((tip, isactive, isopen))
954 954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
955 955 reverse=True)
956 956
957 957 for ctx, isactive, isopen in branches:
958 958 if (not active) or isactive:
959 959 if isactive:
960 960 label = 'branches.active'
961 961 notice = ''
962 962 elif not isopen:
963 963 if not closed:
964 964 continue
965 965 label = 'branches.closed'
966 966 notice = _(' (closed)')
967 967 else:
968 968 label = 'branches.inactive'
969 969 notice = _(' (inactive)')
970 970 if ctx.branch() == repo.dirstate.branch():
971 971 label = 'branches.current'
972 972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
973 973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
974 974 'log.changeset')
975 975 tag = ui.label(ctx.branch(), label)
976 976 if ui.quiet:
977 977 ui.write("%s\n" % tag)
978 978 else:
979 979 ui.write("%s %s%s\n" % (tag, rev, notice))
980 980
981 981 @command('bundle',
982 982 [('f', 'force', None, _('run even when the destination is unrelated')),
983 983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
984 984 _('REV')),
985 985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
986 986 _('BRANCH')),
987 987 ('', 'base', [],
988 988 _('a base changeset assumed to be available at the destination'),
989 989 _('REV')),
990 990 ('a', 'all', None, _('bundle all changesets in the repository')),
991 991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
992 992 ] + remoteopts,
993 993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
994 994 def bundle(ui, repo, fname, dest=None, **opts):
995 995 """create a changegroup file
996 996
997 997 Generate a compressed changegroup file collecting changesets not
998 998 known to be in another repository.
999 999
1000 1000 If you omit the destination repository, then hg assumes the
1001 1001 destination will have all the nodes you specify with --base
1002 1002 parameters. To create a bundle containing all changesets, use
1003 1003 -a/--all (or --base null).
1004 1004
1005 1005 You can change compression method with the -t/--type option.
1006 1006 The available compression methods are: none, bzip2, and
1007 1007 gzip (by default, bundles are compressed using bzip2).
1008 1008
1009 1009 The bundle file can then be transferred using conventional means
1010 1010 and applied to another repository with the unbundle or pull
1011 1011 command. This is useful when direct push and pull are not
1012 1012 available or when exporting an entire repository is undesirable.
1013 1013
1014 1014 Applying bundles preserves all changeset contents including
1015 1015 permissions, copy/rename information, and revision history.
1016 1016
1017 1017 Returns 0 on success, 1 if no changes found.
1018 1018 """
1019 1019 revs = None
1020 1020 if 'rev' in opts:
1021 1021 revs = scmutil.revrange(repo, opts['rev'])
1022 1022
1023 1023 bundletype = opts.get('type', 'bzip2').lower()
1024 1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1025 1025 bundletype = btypes.get(bundletype)
1026 1026 if bundletype not in changegroup.bundletypes:
1027 1027 raise util.Abort(_('unknown bundle type specified with --type'))
1028 1028
1029 1029 if opts.get('all'):
1030 1030 base = ['null']
1031 1031 else:
1032 1032 base = scmutil.revrange(repo, opts.get('base'))
1033 1033 if base:
1034 1034 if dest:
1035 1035 raise util.Abort(_("--base is incompatible with specifying "
1036 1036 "a destination"))
1037 1037 common = [repo.lookup(rev) for rev in base]
1038 1038 heads = revs and map(repo.lookup, revs) or revs
1039 1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1040 1040 outgoing = None
1041 1041 else:
1042 1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1043 1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1044 1044 other = hg.peer(repo, opts, dest)
1045 1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1046 1046 heads = revs and map(repo.lookup, revs) or revs
1047 1047 outgoing = discovery.findcommonoutgoing(repo, other,
1048 1048 onlyheads=heads,
1049 1049 force=opts.get('force'),
1050 1050 portable=True)
1051 1051 cg = repo.getlocalbundle('bundle', outgoing)
1052 1052 if not cg:
1053 1053 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1054 1054 return 1
1055 1055
1056 1056 changegroup.writebundle(cg, fname, bundletype)
1057 1057
1058 1058 @command('cat',
1059 1059 [('o', 'output', '',
1060 1060 _('print output to file with formatted name'), _('FORMAT')),
1061 1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1062 1062 ('', 'decode', None, _('apply any matching decode filter')),
1063 1063 ] + walkopts,
1064 1064 _('[OPTION]... FILE...'))
1065 1065 def cat(ui, repo, file1, *pats, **opts):
1066 1066 """output the current or given revision of files
1067 1067
1068 1068 Print the specified files as they were at the given revision. If
1069 1069 no revision is given, the parent of the working directory is used,
1070 1070 or tip if no revision is checked out.
1071 1071
1072 1072 Output may be to a file, in which case the name of the file is
1073 1073 given using a format string. The formatting rules are the same as
1074 1074 for the export command, with the following additions:
1075 1075
1076 1076 :``%s``: basename of file being printed
1077 1077 :``%d``: dirname of file being printed, or '.' if in repository root
1078 1078 :``%p``: root-relative path name of file being printed
1079 1079
1080 1080 Returns 0 on success.
1081 1081 """
1082 1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1083 1083 err = 1
1084 1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1085 1085 for abs in ctx.walk(m):
1086 1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1087 1087 pathname=abs)
1088 1088 data = ctx[abs].data()
1089 1089 if opts.get('decode'):
1090 1090 data = repo.wwritedata(abs, data)
1091 1091 fp.write(data)
1092 1092 fp.close()
1093 1093 err = 0
1094 1094 return err
1095 1095
1096 1096 @command('^clone',
1097 1097 [('U', 'noupdate', None,
1098 1098 _('the clone will include an empty working copy (only a repository)')),
1099 1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1100 1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1101 1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1102 1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1103 1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1104 1104 ] + remoteopts,
1105 1105 _('[OPTION]... SOURCE [DEST]'))
1106 1106 def clone(ui, source, dest=None, **opts):
1107 1107 """make a copy of an existing repository
1108 1108
1109 1109 Create a copy of an existing repository in a new directory.
1110 1110
1111 1111 If no destination directory name is specified, it defaults to the
1112 1112 basename of the source.
1113 1113
1114 1114 The location of the source is added to the new repository's
1115 1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1116 1116
1117 1117 Only local paths and ``ssh://`` URLs are supported as
1118 1118 destinations. For ``ssh://`` destinations, no working directory or
1119 1119 ``.hg/hgrc`` will be created on the remote side.
1120 1120
1121 1121 To pull only a subset of changesets, specify one or more revisions
1122 1122 identifiers with -r/--rev or branches with -b/--branch. The
1123 1123 resulting clone will contain only the specified changesets and
1124 1124 their ancestors. These options (or 'clone src#rev dest') imply
1125 1125 --pull, even for local source repositories. Note that specifying a
1126 1126 tag will include the tagged changeset but not the changeset
1127 1127 containing the tag.
1128 1128
1129 1129 To check out a particular version, use -u/--update, or
1130 1130 -U/--noupdate to create a clone with no working directory.
1131 1131
1132 1132 .. container:: verbose
1133 1133
1134 1134 For efficiency, hardlinks are used for cloning whenever the
1135 1135 source and destination are on the same filesystem (note this
1136 1136 applies only to the repository data, not to the working
1137 1137 directory). Some filesystems, such as AFS, implement hardlinking
1138 1138 incorrectly, but do not report errors. In these cases, use the
1139 1139 --pull option to avoid hardlinking.
1140 1140
1141 1141 In some cases, you can clone repositories and the working
1142 1142 directory using full hardlinks with ::
1143 1143
1144 1144 $ cp -al REPO REPOCLONE
1145 1145
1146 1146 This is the fastest way to clone, but it is not always safe. The
1147 1147 operation is not atomic (making sure REPO is not modified during
1148 1148 the operation is up to you) and you have to make sure your
1149 1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1150 1150 so). Also, this is not compatible with certain extensions that
1151 1151 place their metadata under the .hg directory, such as mq.
1152 1152
1153 1153 Mercurial will update the working directory to the first applicable
1154 1154 revision from this list:
1155 1155
1156 1156 a) null if -U or the source repository has no changesets
1157 1157 b) if -u . and the source repository is local, the first parent of
1158 1158 the source repository's working directory
1159 1159 c) the changeset specified with -u (if a branch name, this means the
1160 1160 latest head of that branch)
1161 1161 d) the changeset specified with -r
1162 1162 e) the tipmost head specified with -b
1163 1163 f) the tipmost head specified with the url#branch source syntax
1164 1164 g) the tipmost head of the default branch
1165 1165 h) tip
1166 1166
1167 1167 Examples:
1168 1168
1169 1169 - clone a remote repository to a new directory named hg/::
1170 1170
1171 1171 hg clone http://selenic.com/hg
1172 1172
1173 1173 - create a lightweight local clone::
1174 1174
1175 1175 hg clone project/ project-feature/
1176 1176
1177 1177 - clone from an absolute path on an ssh server (note double-slash)::
1178 1178
1179 1179 hg clone ssh://user@server//home/projects/alpha/
1180 1180
1181 1181 - do a high-speed clone over a LAN while checking out a
1182 1182 specified version::
1183 1183
1184 1184 hg clone --uncompressed http://server/repo -u 1.5
1185 1185
1186 1186 - create a repository without changesets after a particular revision::
1187 1187
1188 1188 hg clone -r 04e544 experimental/ good/
1189 1189
1190 1190 - clone (and track) a particular named branch::
1191 1191
1192 1192 hg clone http://selenic.com/hg#stable
1193 1193
1194 1194 See :hg:`help urls` for details on specifying URLs.
1195 1195
1196 1196 Returns 0 on success.
1197 1197 """
1198 1198 if opts.get('noupdate') and opts.get('updaterev'):
1199 1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1200 1200
1201 1201 r = hg.clone(ui, opts, source, dest,
1202 1202 pull=opts.get('pull'),
1203 1203 stream=opts.get('uncompressed'),
1204 1204 rev=opts.get('rev'),
1205 1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1206 1206 branch=opts.get('branch'))
1207 1207
1208 1208 return r is None
1209 1209
1210 1210 @command('^commit|ci',
1211 1211 [('A', 'addremove', None,
1212 1212 _('mark new/missing files as added/removed before committing')),
1213 1213 ('', 'close-branch', None,
1214 1214 _('mark a branch as closed, hiding it from the branch list')),
1215 1215 ('', 'amend', None, _('amend the parent of the working dir')),
1216 1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1217 1217 _('[OPTION]... [FILE]...'))
1218 1218 def commit(ui, repo, *pats, **opts):
1219 1219 """commit the specified files or all outstanding changes
1220 1220
1221 1221 Commit changes to the given files into the repository. Unlike a
1222 1222 centralized SCM, this operation is a local operation. See
1223 1223 :hg:`push` for a way to actively distribute your changes.
1224 1224
1225 1225 If a list of files is omitted, all changes reported by :hg:`status`
1226 1226 will be committed.
1227 1227
1228 1228 If you are committing the result of a merge, do not provide any
1229 1229 filenames or -I/-X filters.
1230 1230
1231 1231 If no commit message is specified, Mercurial starts your
1232 1232 configured editor where you can enter a message. In case your
1233 1233 commit fails, you will find a backup of your message in
1234 1234 ``.hg/last-message.txt``.
1235 1235
1236 1236 The --amend flag can be used to amend the parent of the
1237 1237 working directory with a new commit that contains the changes
1238 1238 in the parent in addition to those currently reported by :hg:`status`,
1239 1239 if there are any. The old commit is stored in a backup bundle in
1240 1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1241 1241 on how to restore it).
1242 1242
1243 1243 Message, user and date are taken from the amended commit unless
1244 1244 specified. When a message isn't specified on the command line,
1245 1245 the editor will open with the message of the amended commit.
1246 1246
1247 1247 It is not possible to amend public changesets (see :hg:`help phases`)
1248 1248 or changesets that have children.
1249 1249
1250 1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1251 1251
1252 1252 Returns 0 on success, 1 if nothing changed.
1253 1253 """
1254 1254 if opts.get('subrepos'):
1255 1255 # Let --subrepos on the command line override config setting.
1256 1256 ui.setconfig('ui', 'commitsubrepos', True)
1257 1257
1258 1258 extra = {}
1259 1259 if opts.get('close_branch'):
1260 1260 if repo['.'].node() not in repo.branchheads():
1261 1261 # The topo heads set is included in the branch heads set of the
1262 1262 # current branch, so it's sufficient to test branchheads
1263 1263 raise util.Abort(_('can only close branch heads'))
1264 1264 extra['close'] = 1
1265 1265
1266 1266 branch = repo[None].branch()
1267 1267 bheads = repo.branchheads(branch)
1268 1268
1269 1269 if opts.get('amend'):
1270 1270 if ui.configbool('ui', 'commitsubrepos'):
1271 1271 raise util.Abort(_('cannot amend recursively'))
1272 1272
1273 1273 old = repo['.']
1274 1274 if old.phase() == phases.public:
1275 1275 raise util.Abort(_('cannot amend public changesets'))
1276 1276 if len(old.parents()) > 1:
1277 1277 raise util.Abort(_('cannot amend merge changesets'))
1278 1278 if len(repo[None].parents()) > 1:
1279 1279 raise util.Abort(_('cannot amend while merging'))
1280 1280 if old.children():
1281 1281 raise util.Abort(_('cannot amend changeset with children'))
1282 1282
1283 1283 e = cmdutil.commiteditor
1284 1284 if opts.get('force_editor'):
1285 1285 e = cmdutil.commitforceeditor
1286 1286
1287 1287 def commitfunc(ui, repo, message, match, opts):
1288 1288 editor = e
1289 1289 # message contains text from -m or -l, if it's empty,
1290 1290 # open the editor with the old message
1291 1291 if not message:
1292 1292 message = old.description()
1293 1293 editor = cmdutil.commitforceeditor
1294 1294 return repo.commit(message,
1295 1295 opts.get('user') or old.user(),
1296 1296 opts.get('date') or old.date(),
1297 1297 match,
1298 1298 editor=editor,
1299 1299 extra=extra)
1300 1300
1301 1301 current = repo._bookmarkcurrent
1302 1302 marks = old.bookmarks()
1303 1303 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1304 1304 if node == old.node():
1305 1305 ui.status(_("nothing changed\n"))
1306 1306 return 1
1307 1307 elif marks:
1308 1308 ui.debug('moving bookmarks %r from %s to %s\n' %
1309 1309 (marks, old.hex(), hex(node)))
1310 1310 for bm in marks:
1311 1311 repo._bookmarks[bm] = node
1312 1312 if bm == current:
1313 1313 bookmarks.setcurrent(repo, bm)
1314 1314 bookmarks.write(repo)
1315 1315 else:
1316 1316 e = cmdutil.commiteditor
1317 1317 if opts.get('force_editor'):
1318 1318 e = cmdutil.commitforceeditor
1319 1319
1320 1320 def commitfunc(ui, repo, message, match, opts):
1321 1321 return repo.commit(message, opts.get('user'), opts.get('date'),
1322 1322 match, editor=e, extra=extra)
1323 1323
1324 1324 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1325 1325
1326 1326 if not node:
1327 1327 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1328 1328 if stat[3]:
1329 1329 ui.status(_("nothing changed (%d missing files, see "
1330 1330 "'hg status')\n") % len(stat[3]))
1331 1331 else:
1332 1332 ui.status(_("nothing changed\n"))
1333 1333 return 1
1334 1334
1335 1335 ctx = repo[node]
1336 1336 parents = ctx.parents()
1337 1337
1338 1338 if (not opts.get('amend') and bheads and node not in bheads and not
1339 1339 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1340 1340 ui.status(_('created new head\n'))
1341 1341 # The message is not printed for initial roots. For the other
1342 1342 # changesets, it is printed in the following situations:
1343 1343 #
1344 1344 # Par column: for the 2 parents with ...
1345 1345 # N: null or no parent
1346 1346 # B: parent is on another named branch
1347 1347 # C: parent is a regular non head changeset
1348 1348 # H: parent was a branch head of the current branch
1349 1349 # Msg column: whether we print "created new head" message
1350 1350 # In the following, it is assumed that there already exists some
1351 1351 # initial branch heads of the current branch, otherwise nothing is
1352 1352 # printed anyway.
1353 1353 #
1354 1354 # Par Msg Comment
1355 # NN y additional topo root
1355 # N N y additional topo root
1356 1356 #
1357 # BN y additional branch root
1358 # CN y additional topo head
1359 # HN n usual case
1357 # B N y additional branch root
1358 # C N y additional topo head
1359 # H N n usual case
1360 1360 #
1361 # BB y weird additional branch root
1362 # CB y branch merge
1363 # HB n merge with named branch
1361 # B B y weird additional branch root
1362 # C B y branch merge
1363 # H B n merge with named branch
1364 1364 #
1365 # CC y additional head from merge
1366 # CH n merge with a head
1365 # C C y additional head from merge
1366 # C H n merge with a head
1367 1367 #
1368 # HH n head merge: head count decreases
1368 # H H n head merge: head count decreases
1369 1369
1370 1370 if not opts.get('close_branch'):
1371 1371 for r in parents:
1372 1372 if r.closesbranch() and r.branch() == branch:
1373 1373 ui.status(_('reopening closed branch head %d\n') % r)
1374 1374
1375 1375 if ui.debugflag:
1376 1376 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1377 1377 elif ui.verbose:
1378 1378 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1379 1379
1380 1380 @command('copy|cp',
1381 1381 [('A', 'after', None, _('record a copy that has already occurred')),
1382 1382 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1383 1383 ] + walkopts + dryrunopts,
1384 1384 _('[OPTION]... [SOURCE]... DEST'))
1385 1385 def copy(ui, repo, *pats, **opts):
1386 1386 """mark files as copied for the next commit
1387 1387
1388 1388 Mark dest as having copies of source files. If dest is a
1389 1389 directory, copies are put in that directory. If dest is a file,
1390 1390 the source must be a single file.
1391 1391
1392 1392 By default, this command copies the contents of files as they
1393 1393 exist in the working directory. If invoked with -A/--after, the
1394 1394 operation is recorded, but no copying is performed.
1395 1395
1396 1396 This command takes effect with the next commit. To undo a copy
1397 1397 before that, see :hg:`revert`.
1398 1398
1399 1399 Returns 0 on success, 1 if errors are encountered.
1400 1400 """
1401 1401 wlock = repo.wlock(False)
1402 1402 try:
1403 1403 return cmdutil.copy(ui, repo, pats, opts)
1404 1404 finally:
1405 1405 wlock.release()
1406 1406
1407 1407 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1408 1408 def debugancestor(ui, repo, *args):
1409 1409 """find the ancestor revision of two revisions in a given index"""
1410 1410 if len(args) == 3:
1411 1411 index, rev1, rev2 = args
1412 1412 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1413 1413 lookup = r.lookup
1414 1414 elif len(args) == 2:
1415 1415 if not repo:
1416 1416 raise util.Abort(_("there is no Mercurial repository here "
1417 1417 "(.hg not found)"))
1418 1418 rev1, rev2 = args
1419 1419 r = repo.changelog
1420 1420 lookup = repo.lookup
1421 1421 else:
1422 1422 raise util.Abort(_('either two or three arguments required'))
1423 1423 a = r.ancestor(lookup(rev1), lookup(rev2))
1424 1424 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1425 1425
1426 1426 @command('debugbuilddag',
1427 1427 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1428 1428 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1429 1429 ('n', 'new-file', None, _('add new file at each rev'))],
1430 1430 _('[OPTION]... [TEXT]'))
1431 1431 def debugbuilddag(ui, repo, text=None,
1432 1432 mergeable_file=False,
1433 1433 overwritten_file=False,
1434 1434 new_file=False):
1435 1435 """builds a repo with a given DAG from scratch in the current empty repo
1436 1436
1437 1437 The description of the DAG is read from stdin if not given on the
1438 1438 command line.
1439 1439
1440 1440 Elements:
1441 1441
1442 1442 - "+n" is a linear run of n nodes based on the current default parent
1443 1443 - "." is a single node based on the current default parent
1444 1444 - "$" resets the default parent to null (implied at the start);
1445 1445 otherwise the default parent is always the last node created
1446 1446 - "<p" sets the default parent to the backref p
1447 1447 - "*p" is a fork at parent p, which is a backref
1448 1448 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1449 1449 - "/p2" is a merge of the preceding node and p2
1450 1450 - ":tag" defines a local tag for the preceding node
1451 1451 - "@branch" sets the named branch for subsequent nodes
1452 1452 - "#...\\n" is a comment up to the end of the line
1453 1453
1454 1454 Whitespace between the above elements is ignored.
1455 1455
1456 1456 A backref is either
1457 1457
1458 1458 - a number n, which references the node curr-n, where curr is the current
1459 1459 node, or
1460 1460 - the name of a local tag you placed earlier using ":tag", or
1461 1461 - empty to denote the default parent.
1462 1462
1463 1463 All string valued-elements are either strictly alphanumeric, or must
1464 1464 be enclosed in double quotes ("..."), with "\\" as escape character.
1465 1465 """
1466 1466
1467 1467 if text is None:
1468 1468 ui.status(_("reading DAG from stdin\n"))
1469 1469 text = ui.fin.read()
1470 1470
1471 1471 cl = repo.changelog
1472 1472 if len(cl) > 0:
1473 1473 raise util.Abort(_('repository is not empty'))
1474 1474
1475 1475 # determine number of revs in DAG
1476 1476 total = 0
1477 1477 for type, data in dagparser.parsedag(text):
1478 1478 if type == 'n':
1479 1479 total += 1
1480 1480
1481 1481 if mergeable_file:
1482 1482 linesperrev = 2
1483 1483 # make a file with k lines per rev
1484 1484 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1485 1485 initialmergedlines.append("")
1486 1486
1487 1487 tags = []
1488 1488
1489 1489 lock = tr = None
1490 1490 try:
1491 1491 lock = repo.lock()
1492 1492 tr = repo.transaction("builddag")
1493 1493
1494 1494 at = -1
1495 1495 atbranch = 'default'
1496 1496 nodeids = []
1497 1497 id = 0
1498 1498 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1499 1499 for type, data in dagparser.parsedag(text):
1500 1500 if type == 'n':
1501 1501 ui.note('node %s\n' % str(data))
1502 1502 id, ps = data
1503 1503
1504 1504 files = []
1505 1505 fctxs = {}
1506 1506
1507 1507 p2 = None
1508 1508 if mergeable_file:
1509 1509 fn = "mf"
1510 1510 p1 = repo[ps[0]]
1511 1511 if len(ps) > 1:
1512 1512 p2 = repo[ps[1]]
1513 1513 pa = p1.ancestor(p2)
1514 1514 base, local, other = [x[fn].data() for x in pa, p1, p2]
1515 1515 m3 = simplemerge.Merge3Text(base, local, other)
1516 1516 ml = [l.strip() for l in m3.merge_lines()]
1517 1517 ml.append("")
1518 1518 elif at > 0:
1519 1519 ml = p1[fn].data().split("\n")
1520 1520 else:
1521 1521 ml = initialmergedlines
1522 1522 ml[id * linesperrev] += " r%i" % id
1523 1523 mergedtext = "\n".join(ml)
1524 1524 files.append(fn)
1525 1525 fctxs[fn] = context.memfilectx(fn, mergedtext)
1526 1526
1527 1527 if overwritten_file:
1528 1528 fn = "of"
1529 1529 files.append(fn)
1530 1530 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1531 1531
1532 1532 if new_file:
1533 1533 fn = "nf%i" % id
1534 1534 files.append(fn)
1535 1535 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1536 1536 if len(ps) > 1:
1537 1537 if not p2:
1538 1538 p2 = repo[ps[1]]
1539 1539 for fn in p2:
1540 1540 if fn.startswith("nf"):
1541 1541 files.append(fn)
1542 1542 fctxs[fn] = p2[fn]
1543 1543
1544 1544 def fctxfn(repo, cx, path):
1545 1545 return fctxs.get(path)
1546 1546
1547 1547 if len(ps) == 0 or ps[0] < 0:
1548 1548 pars = [None, None]
1549 1549 elif len(ps) == 1:
1550 1550 pars = [nodeids[ps[0]], None]
1551 1551 else:
1552 1552 pars = [nodeids[p] for p in ps]
1553 1553 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1554 1554 date=(id, 0),
1555 1555 user="debugbuilddag",
1556 1556 extra={'branch': atbranch})
1557 1557 nodeid = repo.commitctx(cx)
1558 1558 nodeids.append(nodeid)
1559 1559 at = id
1560 1560 elif type == 'l':
1561 1561 id, name = data
1562 1562 ui.note('tag %s\n' % name)
1563 1563 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1564 1564 elif type == 'a':
1565 1565 ui.note('branch %s\n' % data)
1566 1566 atbranch = data
1567 1567 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1568 1568 tr.close()
1569 1569
1570 1570 if tags:
1571 1571 repo.opener.write("localtags", "".join(tags))
1572 1572 finally:
1573 1573 ui.progress(_('building'), None)
1574 1574 release(tr, lock)
1575 1575
1576 1576 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1577 1577 def debugbundle(ui, bundlepath, all=None, **opts):
1578 1578 """lists the contents of a bundle"""
1579 1579 f = url.open(ui, bundlepath)
1580 1580 try:
1581 1581 gen = changegroup.readbundle(f, bundlepath)
1582 1582 if all:
1583 1583 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1584 1584
1585 1585 def showchunks(named):
1586 1586 ui.write("\n%s\n" % named)
1587 1587 chain = None
1588 1588 while True:
1589 1589 chunkdata = gen.deltachunk(chain)
1590 1590 if not chunkdata:
1591 1591 break
1592 1592 node = chunkdata['node']
1593 1593 p1 = chunkdata['p1']
1594 1594 p2 = chunkdata['p2']
1595 1595 cs = chunkdata['cs']
1596 1596 deltabase = chunkdata['deltabase']
1597 1597 delta = chunkdata['delta']
1598 1598 ui.write("%s %s %s %s %s %s\n" %
1599 1599 (hex(node), hex(p1), hex(p2),
1600 1600 hex(cs), hex(deltabase), len(delta)))
1601 1601 chain = node
1602 1602
1603 1603 chunkdata = gen.changelogheader()
1604 1604 showchunks("changelog")
1605 1605 chunkdata = gen.manifestheader()
1606 1606 showchunks("manifest")
1607 1607 while True:
1608 1608 chunkdata = gen.filelogheader()
1609 1609 if not chunkdata:
1610 1610 break
1611 1611 fname = chunkdata['filename']
1612 1612 showchunks(fname)
1613 1613 else:
1614 1614 chunkdata = gen.changelogheader()
1615 1615 chain = None
1616 1616 while True:
1617 1617 chunkdata = gen.deltachunk(chain)
1618 1618 if not chunkdata:
1619 1619 break
1620 1620 node = chunkdata['node']
1621 1621 ui.write("%s\n" % hex(node))
1622 1622 chain = node
1623 1623 finally:
1624 1624 f.close()
1625 1625
1626 1626 @command('debugcheckstate', [], '')
1627 1627 def debugcheckstate(ui, repo):
1628 1628 """validate the correctness of the current dirstate"""
1629 1629 parent1, parent2 = repo.dirstate.parents()
1630 1630 m1 = repo[parent1].manifest()
1631 1631 m2 = repo[parent2].manifest()
1632 1632 errors = 0
1633 1633 for f in repo.dirstate:
1634 1634 state = repo.dirstate[f]
1635 1635 if state in "nr" and f not in m1:
1636 1636 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1637 1637 errors += 1
1638 1638 if state in "a" and f in m1:
1639 1639 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1640 1640 errors += 1
1641 1641 if state in "m" and f not in m1 and f not in m2:
1642 1642 ui.warn(_("%s in state %s, but not in either manifest\n") %
1643 1643 (f, state))
1644 1644 errors += 1
1645 1645 for f in m1:
1646 1646 state = repo.dirstate[f]
1647 1647 if state not in "nrm":
1648 1648 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1649 1649 errors += 1
1650 1650 if errors:
1651 1651 error = _(".hg/dirstate inconsistent with current parent's manifest")
1652 1652 raise util.Abort(error)
1653 1653
1654 1654 @command('debugcommands', [], _('[COMMAND]'))
1655 1655 def debugcommands(ui, cmd='', *args):
1656 1656 """list all available commands and options"""
1657 1657 for cmd, vals in sorted(table.iteritems()):
1658 1658 cmd = cmd.split('|')[0].strip('^')
1659 1659 opts = ', '.join([i[1] for i in vals[1]])
1660 1660 ui.write('%s: %s\n' % (cmd, opts))
1661 1661
1662 1662 @command('debugcomplete',
1663 1663 [('o', 'options', None, _('show the command options'))],
1664 1664 _('[-o] CMD'))
1665 1665 def debugcomplete(ui, cmd='', **opts):
1666 1666 """returns the completion list associated with the given command"""
1667 1667
1668 1668 if opts.get('options'):
1669 1669 options = []
1670 1670 otables = [globalopts]
1671 1671 if cmd:
1672 1672 aliases, entry = cmdutil.findcmd(cmd, table, False)
1673 1673 otables.append(entry[1])
1674 1674 for t in otables:
1675 1675 for o in t:
1676 1676 if "(DEPRECATED)" in o[3]:
1677 1677 continue
1678 1678 if o[0]:
1679 1679 options.append('-%s' % o[0])
1680 1680 options.append('--%s' % o[1])
1681 1681 ui.write("%s\n" % "\n".join(options))
1682 1682 return
1683 1683
1684 1684 cmdlist = cmdutil.findpossible(cmd, table)
1685 1685 if ui.verbose:
1686 1686 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1687 1687 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1688 1688
1689 1689 @command('debugdag',
1690 1690 [('t', 'tags', None, _('use tags as labels')),
1691 1691 ('b', 'branches', None, _('annotate with branch names')),
1692 1692 ('', 'dots', None, _('use dots for runs')),
1693 1693 ('s', 'spaces', None, _('separate elements by spaces'))],
1694 1694 _('[OPTION]... [FILE [REV]...]'))
1695 1695 def debugdag(ui, repo, file_=None, *revs, **opts):
1696 1696 """format the changelog or an index DAG as a concise textual description
1697 1697
1698 1698 If you pass a revlog index, the revlog's DAG is emitted. If you list
1699 1699 revision numbers, they get labelled in the output as rN.
1700 1700
1701 1701 Otherwise, the changelog DAG of the current repo is emitted.
1702 1702 """
1703 1703 spaces = opts.get('spaces')
1704 1704 dots = opts.get('dots')
1705 1705 if file_:
1706 1706 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1707 1707 revs = set((int(r) for r in revs))
1708 1708 def events():
1709 1709 for r in rlog:
1710 1710 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1711 1711 if p != -1)))
1712 1712 if r in revs:
1713 1713 yield 'l', (r, "r%i" % r)
1714 1714 elif repo:
1715 1715 cl = repo.changelog
1716 1716 tags = opts.get('tags')
1717 1717 branches = opts.get('branches')
1718 1718 if tags:
1719 1719 labels = {}
1720 1720 for l, n in repo.tags().items():
1721 1721 labels.setdefault(cl.rev(n), []).append(l)
1722 1722 def events():
1723 1723 b = "default"
1724 1724 for r in cl:
1725 1725 if branches:
1726 1726 newb = cl.read(cl.node(r))[5]['branch']
1727 1727 if newb != b:
1728 1728 yield 'a', newb
1729 1729 b = newb
1730 1730 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1731 1731 if p != -1)))
1732 1732 if tags:
1733 1733 ls = labels.get(r)
1734 1734 if ls:
1735 1735 for l in ls:
1736 1736 yield 'l', (r, l)
1737 1737 else:
1738 1738 raise util.Abort(_('need repo for changelog dag'))
1739 1739
1740 1740 for line in dagparser.dagtextlines(events(),
1741 1741 addspaces=spaces,
1742 1742 wraplabels=True,
1743 1743 wrapannotations=True,
1744 1744 wrapnonlinear=dots,
1745 1745 usedots=dots,
1746 1746 maxlinewidth=70):
1747 1747 ui.write(line)
1748 1748 ui.write("\n")
1749 1749
1750 1750 @command('debugdata',
1751 1751 [('c', 'changelog', False, _('open changelog')),
1752 1752 ('m', 'manifest', False, _('open manifest'))],
1753 1753 _('-c|-m|FILE REV'))
1754 1754 def debugdata(ui, repo, file_, rev = None, **opts):
1755 1755 """dump the contents of a data file revision"""
1756 1756 if opts.get('changelog') or opts.get('manifest'):
1757 1757 file_, rev = None, file_
1758 1758 elif rev is None:
1759 1759 raise error.CommandError('debugdata', _('invalid arguments'))
1760 1760 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1761 1761 try:
1762 1762 ui.write(r.revision(r.lookup(rev)))
1763 1763 except KeyError:
1764 1764 raise util.Abort(_('invalid revision identifier %s') % rev)
1765 1765
1766 1766 @command('debugdate',
1767 1767 [('e', 'extended', None, _('try extended date formats'))],
1768 1768 _('[-e] DATE [RANGE]'))
1769 1769 def debugdate(ui, date, range=None, **opts):
1770 1770 """parse and display a date"""
1771 1771 if opts["extended"]:
1772 1772 d = util.parsedate(date, util.extendeddateformats)
1773 1773 else:
1774 1774 d = util.parsedate(date)
1775 1775 ui.write("internal: %s %s\n" % d)
1776 1776 ui.write("standard: %s\n" % util.datestr(d))
1777 1777 if range:
1778 1778 m = util.matchdate(range)
1779 1779 ui.write("match: %s\n" % m(d[0]))
1780 1780
1781 1781 @command('debugdiscovery',
1782 1782 [('', 'old', None, _('use old-style discovery')),
1783 1783 ('', 'nonheads', None,
1784 1784 _('use old-style discovery with non-heads included')),
1785 1785 ] + remoteopts,
1786 1786 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1787 1787 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1788 1788 """runs the changeset discovery protocol in isolation"""
1789 1789 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1790 1790 opts.get('branch'))
1791 1791 remote = hg.peer(repo, opts, remoteurl)
1792 1792 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1793 1793
1794 1794 # make sure tests are repeatable
1795 1795 random.seed(12323)
1796 1796
1797 1797 def doit(localheads, remoteheads, remote=remote):
1798 1798 if opts.get('old'):
1799 1799 if localheads:
1800 1800 raise util.Abort('cannot use localheads with old style '
1801 1801 'discovery')
1802 1802 if not util.safehasattr(remote, 'branches'):
1803 1803 # enable in-client legacy support
1804 1804 remote = localrepo.locallegacypeer(remote.local())
1805 1805 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1806 1806 force=True)
1807 1807 common = set(common)
1808 1808 if not opts.get('nonheads'):
1809 1809 ui.write("unpruned common: %s\n" % " ".join([short(n)
1810 1810 for n in common]))
1811 1811 dag = dagutil.revlogdag(repo.changelog)
1812 1812 all = dag.ancestorset(dag.internalizeall(common))
1813 1813 common = dag.externalizeall(dag.headsetofconnecteds(all))
1814 1814 else:
1815 1815 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1816 1816 common = set(common)
1817 1817 rheads = set(hds)
1818 1818 lheads = set(repo.heads())
1819 1819 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1820 1820 if lheads <= common:
1821 1821 ui.write("local is subset\n")
1822 1822 elif rheads <= common:
1823 1823 ui.write("remote is subset\n")
1824 1824
1825 1825 serverlogs = opts.get('serverlog')
1826 1826 if serverlogs:
1827 1827 for filename in serverlogs:
1828 1828 logfile = open(filename, 'r')
1829 1829 try:
1830 1830 line = logfile.readline()
1831 1831 while line:
1832 1832 parts = line.strip().split(';')
1833 1833 op = parts[1]
1834 1834 if op == 'cg':
1835 1835 pass
1836 1836 elif op == 'cgss':
1837 1837 doit(parts[2].split(' '), parts[3].split(' '))
1838 1838 elif op == 'unb':
1839 1839 doit(parts[3].split(' '), parts[2].split(' '))
1840 1840 line = logfile.readline()
1841 1841 finally:
1842 1842 logfile.close()
1843 1843
1844 1844 else:
1845 1845 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1846 1846 opts.get('remote_head'))
1847 1847 localrevs = opts.get('local_head')
1848 1848 doit(localrevs, remoterevs)
1849 1849
1850 1850 @command('debugfileset',
1851 1851 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1852 1852 _('[-r REV] FILESPEC'))
1853 1853 def debugfileset(ui, repo, expr, **opts):
1854 1854 '''parse and apply a fileset specification'''
1855 1855 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1856 1856 if ui.verbose:
1857 1857 tree = fileset.parse(expr)[0]
1858 1858 ui.note(tree, "\n")
1859 1859
1860 1860 for f in fileset.getfileset(ctx, expr):
1861 1861 ui.write("%s\n" % f)
1862 1862
1863 1863 @command('debugfsinfo', [], _('[PATH]'))
1864 1864 def debugfsinfo(ui, path = "."):
1865 1865 """show information detected about current filesystem"""
1866 1866 util.writefile('.debugfsinfo', '')
1867 1867 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1868 1868 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1869 1869 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1870 1870 and 'yes' or 'no'))
1871 1871 os.unlink('.debugfsinfo')
1872 1872
1873 1873 @command('debuggetbundle',
1874 1874 [('H', 'head', [], _('id of head node'), _('ID')),
1875 1875 ('C', 'common', [], _('id of common node'), _('ID')),
1876 1876 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1877 1877 _('REPO FILE [-H|-C ID]...'))
1878 1878 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1879 1879 """retrieves a bundle from a repo
1880 1880
1881 1881 Every ID must be a full-length hex node id string. Saves the bundle to the
1882 1882 given file.
1883 1883 """
1884 1884 repo = hg.peer(ui, opts, repopath)
1885 1885 if not repo.capable('getbundle'):
1886 1886 raise util.Abort("getbundle() not supported by target repository")
1887 1887 args = {}
1888 1888 if common:
1889 1889 args['common'] = [bin(s) for s in common]
1890 1890 if head:
1891 1891 args['heads'] = [bin(s) for s in head]
1892 1892 bundle = repo.getbundle('debug', **args)
1893 1893
1894 1894 bundletype = opts.get('type', 'bzip2').lower()
1895 1895 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1896 1896 bundletype = btypes.get(bundletype)
1897 1897 if bundletype not in changegroup.bundletypes:
1898 1898 raise util.Abort(_('unknown bundle type specified with --type'))
1899 1899 changegroup.writebundle(bundle, bundlepath, bundletype)
1900 1900
1901 1901 @command('debugignore', [], '')
1902 1902 def debugignore(ui, repo, *values, **opts):
1903 1903 """display the combined ignore pattern"""
1904 1904 ignore = repo.dirstate._ignore
1905 1905 includepat = getattr(ignore, 'includepat', None)
1906 1906 if includepat is not None:
1907 1907 ui.write("%s\n" % includepat)
1908 1908 else:
1909 1909 raise util.Abort(_("no ignore patterns found"))
1910 1910
1911 1911 @command('debugindex',
1912 1912 [('c', 'changelog', False, _('open changelog')),
1913 1913 ('m', 'manifest', False, _('open manifest')),
1914 1914 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1915 1915 _('[-f FORMAT] -c|-m|FILE'))
1916 1916 def debugindex(ui, repo, file_ = None, **opts):
1917 1917 """dump the contents of an index file"""
1918 1918 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1919 1919 format = opts.get('format', 0)
1920 1920 if format not in (0, 1):
1921 1921 raise util.Abort(_("unknown format %d") % format)
1922 1922
1923 1923 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1924 1924 if generaldelta:
1925 1925 basehdr = ' delta'
1926 1926 else:
1927 1927 basehdr = ' base'
1928 1928
1929 1929 if format == 0:
1930 1930 ui.write(" rev offset length " + basehdr + " linkrev"
1931 1931 " nodeid p1 p2\n")
1932 1932 elif format == 1:
1933 1933 ui.write(" rev flag offset length"
1934 1934 " size " + basehdr + " link p1 p2"
1935 1935 " nodeid\n")
1936 1936
1937 1937 for i in r:
1938 1938 node = r.node(i)
1939 1939 if generaldelta:
1940 1940 base = r.deltaparent(i)
1941 1941 else:
1942 1942 base = r.chainbase(i)
1943 1943 if format == 0:
1944 1944 try:
1945 1945 pp = r.parents(node)
1946 1946 except Exception:
1947 1947 pp = [nullid, nullid]
1948 1948 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1949 1949 i, r.start(i), r.length(i), base, r.linkrev(i),
1950 1950 short(node), short(pp[0]), short(pp[1])))
1951 1951 elif format == 1:
1952 1952 pr = r.parentrevs(i)
1953 1953 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1954 1954 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1955 1955 base, r.linkrev(i), pr[0], pr[1], short(node)))
1956 1956
1957 1957 @command('debugindexdot', [], _('FILE'))
1958 1958 def debugindexdot(ui, repo, file_):
1959 1959 """dump an index DAG as a graphviz dot file"""
1960 1960 r = None
1961 1961 if repo:
1962 1962 filelog = repo.file(file_)
1963 1963 if len(filelog):
1964 1964 r = filelog
1965 1965 if not r:
1966 1966 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1967 1967 ui.write("digraph G {\n")
1968 1968 for i in r:
1969 1969 node = r.node(i)
1970 1970 pp = r.parents(node)
1971 1971 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1972 1972 if pp[1] != nullid:
1973 1973 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1974 1974 ui.write("}\n")
1975 1975
1976 1976 @command('debuginstall', [], '')
1977 1977 def debuginstall(ui):
1978 1978 '''test Mercurial installation
1979 1979
1980 1980 Returns 0 on success.
1981 1981 '''
1982 1982
1983 1983 def writetemp(contents):
1984 1984 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1985 1985 f = os.fdopen(fd, "wb")
1986 1986 f.write(contents)
1987 1987 f.close()
1988 1988 return name
1989 1989
1990 1990 problems = 0
1991 1991
1992 1992 # encoding
1993 1993 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1994 1994 try:
1995 1995 encoding.fromlocal("test")
1996 1996 except util.Abort, inst:
1997 1997 ui.write(" %s\n" % inst)
1998 1998 ui.write(_(" (check that your locale is properly set)\n"))
1999 1999 problems += 1
2000 2000
2001 2001 # Python lib
2002 2002 ui.status(_("checking Python lib (%s)...\n")
2003 2003 % os.path.dirname(os.__file__))
2004 2004
2005 2005 # compiled modules
2006 2006 ui.status(_("checking installed modules (%s)...\n")
2007 2007 % os.path.dirname(__file__))
2008 2008 try:
2009 2009 import bdiff, mpatch, base85, osutil
2010 2010 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2011 2011 except Exception, inst:
2012 2012 ui.write(" %s\n" % inst)
2013 2013 ui.write(_(" One or more extensions could not be found"))
2014 2014 ui.write(_(" (check that you compiled the extensions)\n"))
2015 2015 problems += 1
2016 2016
2017 2017 # templates
2018 2018 import templater
2019 2019 p = templater.templatepath()
2020 2020 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2021 2021 try:
2022 2022 templater.templater(templater.templatepath("map-cmdline.default"))
2023 2023 except Exception, inst:
2024 2024 ui.write(" %s\n" % inst)
2025 2025 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2026 2026 problems += 1
2027 2027
2028 2028 # editor
2029 2029 ui.status(_("checking commit editor...\n"))
2030 2030 editor = ui.geteditor()
2031 2031 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2032 2032 if not cmdpath:
2033 2033 if editor == 'vi':
2034 2034 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2035 2035 ui.write(_(" (specify a commit editor in your configuration"
2036 2036 " file)\n"))
2037 2037 else:
2038 2038 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2039 2039 ui.write(_(" (specify a commit editor in your configuration"
2040 2040 " file)\n"))
2041 2041 problems += 1
2042 2042
2043 2043 # check username
2044 2044 ui.status(_("checking username...\n"))
2045 2045 try:
2046 2046 ui.username()
2047 2047 except util.Abort, e:
2048 2048 ui.write(" %s\n" % e)
2049 2049 ui.write(_(" (specify a username in your configuration file)\n"))
2050 2050 problems += 1
2051 2051
2052 2052 if not problems:
2053 2053 ui.status(_("no problems detected\n"))
2054 2054 else:
2055 2055 ui.write(_("%s problems detected,"
2056 2056 " please check your install!\n") % problems)
2057 2057
2058 2058 return problems
2059 2059
2060 2060 @command('debugknown', [], _('REPO ID...'))
2061 2061 def debugknown(ui, repopath, *ids, **opts):
2062 2062 """test whether node ids are known to a repo
2063 2063
2064 2064 Every ID must be a full-length hex node id string. Returns a list of 0s
2065 2065 and 1s indicating unknown/known.
2066 2066 """
2067 2067 repo = hg.peer(ui, opts, repopath)
2068 2068 if not repo.capable('known'):
2069 2069 raise util.Abort("known() not supported by target repository")
2070 2070 flags = repo.known([bin(s) for s in ids])
2071 2071 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2072 2072
2073 2073 @command('debugobsolete', [] + commitopts2,
2074 2074 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2075 2075 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2076 2076 """create arbitrary obsolete marker"""
2077 2077 def parsenodeid(s):
2078 2078 try:
2079 2079 # We do not use revsingle/revrange functions here to accept
2080 2080 # arbitrary node identifiers, possibly not present in the
2081 2081 # local repository.
2082 2082 n = bin(s)
2083 2083 if len(n) != len(nullid):
2084 2084 raise TypeError()
2085 2085 return n
2086 2086 except TypeError:
2087 2087 raise util.Abort('changeset references must be full hexadecimal '
2088 2088 'node identifiers')
2089 2089
2090 2090 if precursor is not None:
2091 2091 metadata = {}
2092 2092 if 'date' in opts:
2093 2093 metadata['date'] = opts['date']
2094 2094 metadata['user'] = opts['user'] or ui.username()
2095 2095 succs = tuple(parsenodeid(succ) for succ in successors)
2096 2096 l = repo.lock()
2097 2097 try:
2098 2098 tr = repo.transaction('debugobsolete')
2099 2099 try:
2100 2100 repo.obsstore.create(tr, parsenodeid(precursor), succs, 0,
2101 2101 metadata)
2102 2102 tr.close()
2103 2103 finally:
2104 2104 tr.release()
2105 2105 finally:
2106 2106 l.release()
2107 2107 else:
2108 2108 for m in obsolete.allmarkers(repo):
2109 2109 ui.write(hex(m.precnode()))
2110 2110 for repl in m.succnodes():
2111 2111 ui.write(' ')
2112 2112 ui.write(hex(repl))
2113 2113 ui.write(' %X ' % m._data[2])
2114 2114 ui.write(m.metadata())
2115 2115 ui.write('\n')
2116 2116
2117 2117 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2118 2118 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2119 2119 '''access the pushkey key/value protocol
2120 2120
2121 2121 With two args, list the keys in the given namespace.
2122 2122
2123 2123 With five args, set a key to new if it currently is set to old.
2124 2124 Reports success or failure.
2125 2125 '''
2126 2126
2127 2127 target = hg.peer(ui, {}, repopath)
2128 2128 if keyinfo:
2129 2129 key, old, new = keyinfo
2130 2130 r = target.pushkey(namespace, key, old, new)
2131 2131 ui.status(str(r) + '\n')
2132 2132 return not r
2133 2133 else:
2134 2134 for k, v in target.listkeys(namespace).iteritems():
2135 2135 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2136 2136 v.encode('string-escape')))
2137 2137
2138 2138 @command('debugpvec', [], _('A B'))
2139 2139 def debugpvec(ui, repo, a, b=None):
2140 2140 ca = scmutil.revsingle(repo, a)
2141 2141 cb = scmutil.revsingle(repo, b)
2142 2142 pa = pvec.ctxpvec(ca)
2143 2143 pb = pvec.ctxpvec(cb)
2144 2144 if pa == pb:
2145 2145 rel = "="
2146 2146 elif pa > pb:
2147 2147 rel = ">"
2148 2148 elif pa < pb:
2149 2149 rel = "<"
2150 2150 elif pa | pb:
2151 2151 rel = "|"
2152 2152 ui.write(_("a: %s\n") % pa)
2153 2153 ui.write(_("b: %s\n") % pb)
2154 2154 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2155 2155 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2156 2156 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2157 2157 pa.distance(pb), rel))
2158 2158
2159 2159 @command('debugrebuildstate',
2160 2160 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2161 2161 _('[-r REV] [REV]'))
2162 2162 def debugrebuildstate(ui, repo, rev="tip"):
2163 2163 """rebuild the dirstate as it would look like for the given revision"""
2164 2164 ctx = scmutil.revsingle(repo, rev)
2165 2165 wlock = repo.wlock()
2166 2166 try:
2167 2167 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2168 2168 finally:
2169 2169 wlock.release()
2170 2170
2171 2171 @command('debugrename',
2172 2172 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2173 2173 _('[-r REV] FILE'))
2174 2174 def debugrename(ui, repo, file1, *pats, **opts):
2175 2175 """dump rename information"""
2176 2176
2177 2177 ctx = scmutil.revsingle(repo, opts.get('rev'))
2178 2178 m = scmutil.match(ctx, (file1,) + pats, opts)
2179 2179 for abs in ctx.walk(m):
2180 2180 fctx = ctx[abs]
2181 2181 o = fctx.filelog().renamed(fctx.filenode())
2182 2182 rel = m.rel(abs)
2183 2183 if o:
2184 2184 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2185 2185 else:
2186 2186 ui.write(_("%s not renamed\n") % rel)
2187 2187
2188 2188 @command('debugrevlog',
2189 2189 [('c', 'changelog', False, _('open changelog')),
2190 2190 ('m', 'manifest', False, _('open manifest')),
2191 2191 ('d', 'dump', False, _('dump index data'))],
2192 2192 _('-c|-m|FILE'))
2193 2193 def debugrevlog(ui, repo, file_ = None, **opts):
2194 2194 """show data and statistics about a revlog"""
2195 2195 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2196 2196
2197 2197 if opts.get("dump"):
2198 2198 numrevs = len(r)
2199 2199 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2200 2200 " rawsize totalsize compression heads\n")
2201 2201 ts = 0
2202 2202 heads = set()
2203 2203 for rev in xrange(numrevs):
2204 2204 dbase = r.deltaparent(rev)
2205 2205 if dbase == -1:
2206 2206 dbase = rev
2207 2207 cbase = r.chainbase(rev)
2208 2208 p1, p2 = r.parentrevs(rev)
2209 2209 rs = r.rawsize(rev)
2210 2210 ts = ts + rs
2211 2211 heads -= set(r.parentrevs(rev))
2212 2212 heads.add(rev)
2213 2213 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2214 2214 (rev, p1, p2, r.start(rev), r.end(rev),
2215 2215 r.start(dbase), r.start(cbase),
2216 2216 r.start(p1), r.start(p2),
2217 2217 rs, ts, ts / r.end(rev), len(heads)))
2218 2218 return 0
2219 2219
2220 2220 v = r.version
2221 2221 format = v & 0xFFFF
2222 2222 flags = []
2223 2223 gdelta = False
2224 2224 if v & revlog.REVLOGNGINLINEDATA:
2225 2225 flags.append('inline')
2226 2226 if v & revlog.REVLOGGENERALDELTA:
2227 2227 gdelta = True
2228 2228 flags.append('generaldelta')
2229 2229 if not flags:
2230 2230 flags = ['(none)']
2231 2231
2232 2232 nummerges = 0
2233 2233 numfull = 0
2234 2234 numprev = 0
2235 2235 nump1 = 0
2236 2236 nump2 = 0
2237 2237 numother = 0
2238 2238 nump1prev = 0
2239 2239 nump2prev = 0
2240 2240 chainlengths = []
2241 2241
2242 2242 datasize = [None, 0, 0L]
2243 2243 fullsize = [None, 0, 0L]
2244 2244 deltasize = [None, 0, 0L]
2245 2245
2246 2246 def addsize(size, l):
2247 2247 if l[0] is None or size < l[0]:
2248 2248 l[0] = size
2249 2249 if size > l[1]:
2250 2250 l[1] = size
2251 2251 l[2] += size
2252 2252
2253 2253 numrevs = len(r)
2254 2254 for rev in xrange(numrevs):
2255 2255 p1, p2 = r.parentrevs(rev)
2256 2256 delta = r.deltaparent(rev)
2257 2257 if format > 0:
2258 2258 addsize(r.rawsize(rev), datasize)
2259 2259 if p2 != nullrev:
2260 2260 nummerges += 1
2261 2261 size = r.length(rev)
2262 2262 if delta == nullrev:
2263 2263 chainlengths.append(0)
2264 2264 numfull += 1
2265 2265 addsize(size, fullsize)
2266 2266 else:
2267 2267 chainlengths.append(chainlengths[delta] + 1)
2268 2268 addsize(size, deltasize)
2269 2269 if delta == rev - 1:
2270 2270 numprev += 1
2271 2271 if delta == p1:
2272 2272 nump1prev += 1
2273 2273 elif delta == p2:
2274 2274 nump2prev += 1
2275 2275 elif delta == p1:
2276 2276 nump1 += 1
2277 2277 elif delta == p2:
2278 2278 nump2 += 1
2279 2279 elif delta != nullrev:
2280 2280 numother += 1
2281 2281
2282 2282 # Adjust size min value for empty cases
2283 2283 for size in (datasize, fullsize, deltasize):
2284 2284 if size[0] is None:
2285 2285 size[0] = 0
2286 2286
2287 2287 numdeltas = numrevs - numfull
2288 2288 numoprev = numprev - nump1prev - nump2prev
2289 2289 totalrawsize = datasize[2]
2290 2290 datasize[2] /= numrevs
2291 2291 fulltotal = fullsize[2]
2292 2292 fullsize[2] /= numfull
2293 2293 deltatotal = deltasize[2]
2294 2294 if numrevs - numfull > 0:
2295 2295 deltasize[2] /= numrevs - numfull
2296 2296 totalsize = fulltotal + deltatotal
2297 2297 avgchainlen = sum(chainlengths) / numrevs
2298 2298 compratio = totalrawsize / totalsize
2299 2299
2300 2300 basedfmtstr = '%%%dd\n'
2301 2301 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2302 2302
2303 2303 def dfmtstr(max):
2304 2304 return basedfmtstr % len(str(max))
2305 2305 def pcfmtstr(max, padding=0):
2306 2306 return basepcfmtstr % (len(str(max)), ' ' * padding)
2307 2307
2308 2308 def pcfmt(value, total):
2309 2309 return (value, 100 * float(value) / total)
2310 2310
2311 2311 ui.write('format : %d\n' % format)
2312 2312 ui.write('flags : %s\n' % ', '.join(flags))
2313 2313
2314 2314 ui.write('\n')
2315 2315 fmt = pcfmtstr(totalsize)
2316 2316 fmt2 = dfmtstr(totalsize)
2317 2317 ui.write('revisions : ' + fmt2 % numrevs)
2318 2318 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2319 2319 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2320 2320 ui.write('revisions : ' + fmt2 % numrevs)
2321 2321 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2322 2322 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2323 2323 ui.write('revision size : ' + fmt2 % totalsize)
2324 2324 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2325 2325 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2326 2326
2327 2327 ui.write('\n')
2328 2328 fmt = dfmtstr(max(avgchainlen, compratio))
2329 2329 ui.write('avg chain length : ' + fmt % avgchainlen)
2330 2330 ui.write('compression ratio : ' + fmt % compratio)
2331 2331
2332 2332 if format > 0:
2333 2333 ui.write('\n')
2334 2334 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2335 2335 % tuple(datasize))
2336 2336 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2337 2337 % tuple(fullsize))
2338 2338 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2339 2339 % tuple(deltasize))
2340 2340
2341 2341 if numdeltas > 0:
2342 2342 ui.write('\n')
2343 2343 fmt = pcfmtstr(numdeltas)
2344 2344 fmt2 = pcfmtstr(numdeltas, 4)
2345 2345 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2346 2346 if numprev > 0:
2347 2347 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2348 2348 numprev))
2349 2349 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2350 2350 numprev))
2351 2351 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2352 2352 numprev))
2353 2353 if gdelta:
2354 2354 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2355 2355 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2356 2356 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2357 2357 numdeltas))
2358 2358
2359 2359 @command('debugrevspec', [], ('REVSPEC'))
2360 2360 def debugrevspec(ui, repo, expr):
2361 2361 """parse and apply a revision specification
2362 2362
2363 2363 Use --verbose to print the parsed tree before and after aliases
2364 2364 expansion.
2365 2365 """
2366 2366 if ui.verbose:
2367 2367 tree = revset.parse(expr)[0]
2368 2368 ui.note(revset.prettyformat(tree), "\n")
2369 2369 newtree = revset.findaliases(ui, tree)
2370 2370 if newtree != tree:
2371 2371 ui.note(revset.prettyformat(newtree), "\n")
2372 2372 func = revset.match(ui, expr)
2373 2373 for c in func(repo, range(len(repo))):
2374 2374 ui.write("%s\n" % c)
2375 2375
2376 2376 @command('debugsetparents', [], _('REV1 [REV2]'))
2377 2377 def debugsetparents(ui, repo, rev1, rev2=None):
2378 2378 """manually set the parents of the current working directory
2379 2379
2380 2380 This is useful for writing repository conversion tools, but should
2381 2381 be used with care.
2382 2382
2383 2383 Returns 0 on success.
2384 2384 """
2385 2385
2386 2386 r1 = scmutil.revsingle(repo, rev1).node()
2387 2387 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2388 2388
2389 2389 wlock = repo.wlock()
2390 2390 try:
2391 2391 repo.setparents(r1, r2)
2392 2392 finally:
2393 2393 wlock.release()
2394 2394
2395 2395 @command('debugstate',
2396 2396 [('', 'nodates', None, _('do not display the saved mtime')),
2397 2397 ('', 'datesort', None, _('sort by saved mtime'))],
2398 2398 _('[OPTION]...'))
2399 2399 def debugstate(ui, repo, nodates=None, datesort=None):
2400 2400 """show the contents of the current dirstate"""
2401 2401 timestr = ""
2402 2402 showdate = not nodates
2403 2403 if datesort:
2404 2404 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2405 2405 else:
2406 2406 keyfunc = None # sort by filename
2407 2407 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2408 2408 if showdate:
2409 2409 if ent[3] == -1:
2410 2410 # Pad or slice to locale representation
2411 2411 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2412 2412 time.localtime(0)))
2413 2413 timestr = 'unset'
2414 2414 timestr = (timestr[:locale_len] +
2415 2415 ' ' * (locale_len - len(timestr)))
2416 2416 else:
2417 2417 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2418 2418 time.localtime(ent[3]))
2419 2419 if ent[1] & 020000:
2420 2420 mode = 'lnk'
2421 2421 else:
2422 2422 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2423 2423 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2424 2424 for f in repo.dirstate.copies():
2425 2425 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2426 2426
2427 2427 @command('debugsub',
2428 2428 [('r', 'rev', '',
2429 2429 _('revision to check'), _('REV'))],
2430 2430 _('[-r REV] [REV]'))
2431 2431 def debugsub(ui, repo, rev=None):
2432 2432 ctx = scmutil.revsingle(repo, rev, None)
2433 2433 for k, v in sorted(ctx.substate.items()):
2434 2434 ui.write('path %s\n' % k)
2435 2435 ui.write(' source %s\n' % v[0])
2436 2436 ui.write(' revision %s\n' % v[1])
2437 2437
2438 2438 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2439 2439 def debugwalk(ui, repo, *pats, **opts):
2440 2440 """show how files match on given patterns"""
2441 2441 m = scmutil.match(repo[None], pats, opts)
2442 2442 items = list(repo.walk(m))
2443 2443 if not items:
2444 2444 return
2445 2445 f = lambda fn: fn
2446 2446 if ui.configbool('ui', 'slash') and os.sep != '/':
2447 2447 f = lambda fn: util.normpath(fn)
2448 2448 fmt = 'f %%-%ds %%-%ds %%s' % (
2449 2449 max([len(abs) for abs in items]),
2450 2450 max([len(m.rel(abs)) for abs in items]))
2451 2451 for abs in items:
2452 2452 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2453 2453 ui.write("%s\n" % line.rstrip())
2454 2454
2455 2455 @command('debugwireargs',
2456 2456 [('', 'three', '', 'three'),
2457 2457 ('', 'four', '', 'four'),
2458 2458 ('', 'five', '', 'five'),
2459 2459 ] + remoteopts,
2460 2460 _('REPO [OPTIONS]... [ONE [TWO]]'))
2461 2461 def debugwireargs(ui, repopath, *vals, **opts):
2462 2462 repo = hg.peer(ui, opts, repopath)
2463 2463 for opt in remoteopts:
2464 2464 del opts[opt[1]]
2465 2465 args = {}
2466 2466 for k, v in opts.iteritems():
2467 2467 if v:
2468 2468 args[k] = v
2469 2469 # run twice to check that we don't mess up the stream for the next command
2470 2470 res1 = repo.debugwireargs(*vals, **args)
2471 2471 res2 = repo.debugwireargs(*vals, **args)
2472 2472 ui.write("%s\n" % res1)
2473 2473 if res1 != res2:
2474 2474 ui.warn("%s\n" % res2)
2475 2475
2476 2476 @command('^diff',
2477 2477 [('r', 'rev', [], _('revision'), _('REV')),
2478 2478 ('c', 'change', '', _('change made by revision'), _('REV'))
2479 2479 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2480 2480 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2481 2481 def diff(ui, repo, *pats, **opts):
2482 2482 """diff repository (or selected files)
2483 2483
2484 2484 Show differences between revisions for the specified files.
2485 2485
2486 2486 Differences between files are shown using the unified diff format.
2487 2487
2488 2488 .. note::
2489 2489 diff may generate unexpected results for merges, as it will
2490 2490 default to comparing against the working directory's first
2491 2491 parent changeset if no revisions are specified.
2492 2492
2493 2493 When two revision arguments are given, then changes are shown
2494 2494 between those revisions. If only one revision is specified then
2495 2495 that revision is compared to the working directory, and, when no
2496 2496 revisions are specified, the working directory files are compared
2497 2497 to its parent.
2498 2498
2499 2499 Alternatively you can specify -c/--change with a revision to see
2500 2500 the changes in that changeset relative to its first parent.
2501 2501
2502 2502 Without the -a/--text option, diff will avoid generating diffs of
2503 2503 files it detects as binary. With -a, diff will generate a diff
2504 2504 anyway, probably with undesirable results.
2505 2505
2506 2506 Use the -g/--git option to generate diffs in the git extended diff
2507 2507 format. For more information, read :hg:`help diffs`.
2508 2508
2509 2509 .. container:: verbose
2510 2510
2511 2511 Examples:
2512 2512
2513 2513 - compare a file in the current working directory to its parent::
2514 2514
2515 2515 hg diff foo.c
2516 2516
2517 2517 - compare two historical versions of a directory, with rename info::
2518 2518
2519 2519 hg diff --git -r 1.0:1.2 lib/
2520 2520
2521 2521 - get change stats relative to the last change on some date::
2522 2522
2523 2523 hg diff --stat -r "date('may 2')"
2524 2524
2525 2525 - diff all newly-added files that contain a keyword::
2526 2526
2527 2527 hg diff "set:added() and grep(GNU)"
2528 2528
2529 2529 - compare a revision and its parents::
2530 2530
2531 2531 hg diff -c 9353 # compare against first parent
2532 2532 hg diff -r 9353^:9353 # same using revset syntax
2533 2533 hg diff -r 9353^2:9353 # compare against the second parent
2534 2534
2535 2535 Returns 0 on success.
2536 2536 """
2537 2537
2538 2538 revs = opts.get('rev')
2539 2539 change = opts.get('change')
2540 2540 stat = opts.get('stat')
2541 2541 reverse = opts.get('reverse')
2542 2542
2543 2543 if revs and change:
2544 2544 msg = _('cannot specify --rev and --change at the same time')
2545 2545 raise util.Abort(msg)
2546 2546 elif change:
2547 2547 node2 = scmutil.revsingle(repo, change, None).node()
2548 2548 node1 = repo[node2].p1().node()
2549 2549 else:
2550 2550 node1, node2 = scmutil.revpair(repo, revs)
2551 2551
2552 2552 if reverse:
2553 2553 node1, node2 = node2, node1
2554 2554
2555 2555 diffopts = patch.diffopts(ui, opts)
2556 2556 m = scmutil.match(repo[node2], pats, opts)
2557 2557 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2558 2558 listsubrepos=opts.get('subrepos'))
2559 2559
2560 2560 @command('^export',
2561 2561 [('o', 'output', '',
2562 2562 _('print output to file with formatted name'), _('FORMAT')),
2563 2563 ('', 'switch-parent', None, _('diff against the second parent')),
2564 2564 ('r', 'rev', [], _('revisions to export'), _('REV')),
2565 2565 ] + diffopts,
2566 2566 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2567 2567 def export(ui, repo, *changesets, **opts):
2568 2568 """dump the header and diffs for one or more changesets
2569 2569
2570 2570 Print the changeset header and diffs for one or more revisions.
2571 2571
2572 2572 The information shown in the changeset header is: author, date,
2573 2573 branch name (if non-default), changeset hash, parent(s) and commit
2574 2574 comment.
2575 2575
2576 2576 .. note::
2577 2577 export may generate unexpected diff output for merge
2578 2578 changesets, as it will compare the merge changeset against its
2579 2579 first parent only.
2580 2580
2581 2581 Output may be to a file, in which case the name of the file is
2582 2582 given using a format string. The formatting rules are as follows:
2583 2583
2584 2584 :``%%``: literal "%" character
2585 2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 2586 :``%N``: number of patches being generated
2587 2587 :``%R``: changeset revision number
2588 2588 :``%b``: basename of the exporting repository
2589 2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 2591 :``%n``: zero-padded sequence number, starting at 1
2592 2592 :``%r``: zero-padded changeset revision number
2593 2593
2594 2594 Without the -a/--text option, export will avoid generating diffs
2595 2595 of files it detects as binary. With -a, export will generate a
2596 2596 diff anyway, probably with undesirable results.
2597 2597
2598 2598 Use the -g/--git option to generate diffs in the git extended diff
2599 2599 format. See :hg:`help diffs` for more information.
2600 2600
2601 2601 With the --switch-parent option, the diff will be against the
2602 2602 second parent. It can be useful to review a merge.
2603 2603
2604 2604 .. container:: verbose
2605 2605
2606 2606 Examples:
2607 2607
2608 2608 - use export and import to transplant a bugfix to the current
2609 2609 branch::
2610 2610
2611 2611 hg export -r 9353 | hg import -
2612 2612
2613 2613 - export all the changesets between two revisions to a file with
2614 2614 rename information::
2615 2615
2616 2616 hg export --git -r 123:150 > changes.txt
2617 2617
2618 2618 - split outgoing changes into a series of patches with
2619 2619 descriptive names::
2620 2620
2621 2621 hg export -r "outgoing()" -o "%n-%m.patch"
2622 2622
2623 2623 Returns 0 on success.
2624 2624 """
2625 2625 changesets += tuple(opts.get('rev', []))
2626 2626 revs = scmutil.revrange(repo, changesets)
2627 2627 if not revs:
2628 2628 raise util.Abort(_("export requires at least one changeset"))
2629 2629 if len(revs) > 1:
2630 2630 ui.note(_('exporting patches:\n'))
2631 2631 else:
2632 2632 ui.note(_('exporting patch:\n'))
2633 2633 cmdutil.export(repo, revs, template=opts.get('output'),
2634 2634 switch_parent=opts.get('switch_parent'),
2635 2635 opts=patch.diffopts(ui, opts))
2636 2636
2637 2637 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2638 2638 def forget(ui, repo, *pats, **opts):
2639 2639 """forget the specified files on the next commit
2640 2640
2641 2641 Mark the specified files so they will no longer be tracked
2642 2642 after the next commit.
2643 2643
2644 2644 This only removes files from the current branch, not from the
2645 2645 entire project history, and it does not delete them from the
2646 2646 working directory.
2647 2647
2648 2648 To undo a forget before the next commit, see :hg:`add`.
2649 2649
2650 2650 .. container:: verbose
2651 2651
2652 2652 Examples:
2653 2653
2654 2654 - forget newly-added binary files::
2655 2655
2656 2656 hg forget "set:added() and binary()"
2657 2657
2658 2658 - forget files that would be excluded by .hgignore::
2659 2659
2660 2660 hg forget "set:hgignore()"
2661 2661
2662 2662 Returns 0 on success.
2663 2663 """
2664 2664
2665 2665 if not pats:
2666 2666 raise util.Abort(_('no files specified'))
2667 2667
2668 2668 m = scmutil.match(repo[None], pats, opts)
2669 2669 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2670 2670 return rejected and 1 or 0
2671 2671
2672 2672 @command(
2673 2673 'graft',
2674 2674 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2675 2675 ('c', 'continue', False, _('resume interrupted graft')),
2676 2676 ('e', 'edit', False, _('invoke editor on commit messages')),
2677 2677 ('', 'log', None, _('append graft info to log message')),
2678 2678 ('D', 'currentdate', False,
2679 2679 _('record the current date as commit date')),
2680 2680 ('U', 'currentuser', False,
2681 2681 _('record the current user as committer'), _('DATE'))]
2682 2682 + commitopts2 + mergetoolopts + dryrunopts,
2683 2683 _('[OPTION]... [-r] REV...'))
2684 2684 def graft(ui, repo, *revs, **opts):
2685 2685 '''copy changes from other branches onto the current branch
2686 2686
2687 2687 This command uses Mercurial's merge logic to copy individual
2688 2688 changes from other branches without merging branches in the
2689 2689 history graph. This is sometimes known as 'backporting' or
2690 2690 'cherry-picking'. By default, graft will copy user, date, and
2691 2691 description from the source changesets.
2692 2692
2693 2693 Changesets that are ancestors of the current revision, that have
2694 2694 already been grafted, or that are merges will be skipped.
2695 2695
2696 2696 If --log is specified, log messages will have a comment appended
2697 2697 of the form::
2698 2698
2699 2699 (grafted from CHANGESETHASH)
2700 2700
2701 2701 If a graft merge results in conflicts, the graft process is
2702 2702 interrupted so that the current merge can be manually resolved.
2703 2703 Once all conflicts are addressed, the graft process can be
2704 2704 continued with the -c/--continue option.
2705 2705
2706 2706 .. note::
2707 2707 The -c/--continue option does not reapply earlier options.
2708 2708
2709 2709 .. container:: verbose
2710 2710
2711 2711 Examples:
2712 2712
2713 2713 - copy a single change to the stable branch and edit its description::
2714 2714
2715 2715 hg update stable
2716 2716 hg graft --edit 9393
2717 2717
2718 2718 - graft a range of changesets with one exception, updating dates::
2719 2719
2720 2720 hg graft -D "2085::2093 and not 2091"
2721 2721
2722 2722 - continue a graft after resolving conflicts::
2723 2723
2724 2724 hg graft -c
2725 2725
2726 2726 - show the source of a grafted changeset::
2727 2727
2728 2728 hg log --debug -r tip
2729 2729
2730 2730 Returns 0 on successful completion.
2731 2731 '''
2732 2732
2733 2733 revs = list(revs)
2734 2734 revs.extend(opts['rev'])
2735 2735
2736 2736 if not opts.get('user') and opts.get('currentuser'):
2737 2737 opts['user'] = ui.username()
2738 2738 if not opts.get('date') and opts.get('currentdate'):
2739 2739 opts['date'] = "%d %d" % util.makedate()
2740 2740
2741 2741 editor = None
2742 2742 if opts.get('edit'):
2743 2743 editor = cmdutil.commitforceeditor
2744 2744
2745 2745 cont = False
2746 2746 if opts['continue']:
2747 2747 cont = True
2748 2748 if revs:
2749 2749 raise util.Abort(_("can't specify --continue and revisions"))
2750 2750 # read in unfinished revisions
2751 2751 try:
2752 2752 nodes = repo.opener.read('graftstate').splitlines()
2753 2753 revs = [repo[node].rev() for node in nodes]
2754 2754 except IOError, inst:
2755 2755 if inst.errno != errno.ENOENT:
2756 2756 raise
2757 2757 raise util.Abort(_("no graft state found, can't continue"))
2758 2758 else:
2759 2759 cmdutil.bailifchanged(repo)
2760 2760 if not revs:
2761 2761 raise util.Abort(_('no revisions specified'))
2762 2762 revs = scmutil.revrange(repo, revs)
2763 2763
2764 2764 # check for merges
2765 2765 for rev in repo.revs('%ld and merge()', revs):
2766 2766 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2767 2767 revs.remove(rev)
2768 2768 if not revs:
2769 2769 return -1
2770 2770
2771 2771 # check for ancestors of dest branch
2772 2772 for rev in repo.revs('::. and %ld', revs):
2773 2773 ui.warn(_('skipping ancestor revision %s\n') % rev)
2774 2774 revs.remove(rev)
2775 2775 if not revs:
2776 2776 return -1
2777 2777
2778 2778 # analyze revs for earlier grafts
2779 2779 ids = {}
2780 2780 for ctx in repo.set("%ld", revs):
2781 2781 ids[ctx.hex()] = ctx.rev()
2782 2782 n = ctx.extra().get('source')
2783 2783 if n:
2784 2784 ids[n] = ctx.rev()
2785 2785
2786 2786 # check ancestors for earlier grafts
2787 2787 ui.debug('scanning for duplicate grafts\n')
2788 2788 for ctx in repo.set("::. - ::%ld", revs):
2789 2789 n = ctx.extra().get('source')
2790 2790 if n in ids:
2791 2791 r = repo[n].rev()
2792 2792 if r in revs:
2793 2793 ui.warn(_('skipping already grafted revision %s\n') % r)
2794 2794 revs.remove(r)
2795 2795 elif ids[n] in revs:
2796 2796 ui.warn(_('skipping already grafted revision %s '
2797 2797 '(same origin %d)\n') % (ids[n], r))
2798 2798 revs.remove(ids[n])
2799 2799 elif ctx.hex() in ids:
2800 2800 r = ids[ctx.hex()]
2801 2801 ui.warn(_('skipping already grafted revision %s '
2802 2802 '(was grafted from %d)\n') % (r, ctx.rev()))
2803 2803 revs.remove(r)
2804 2804 if not revs:
2805 2805 return -1
2806 2806
2807 2807 wlock = repo.wlock()
2808 2808 try:
2809 2809 for pos, ctx in enumerate(repo.set("%ld", revs)):
2810 2810 current = repo['.']
2811 2811
2812 2812 ui.status(_('grafting revision %s\n') % ctx.rev())
2813 2813 if opts.get('dry_run'):
2814 2814 continue
2815 2815
2816 2816 # we don't merge the first commit when continuing
2817 2817 if not cont:
2818 2818 # perform the graft merge with p1(rev) as 'ancestor'
2819 2819 try:
2820 2820 # ui.forcemerge is an internal variable, do not document
2821 2821 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2822 2822 stats = mergemod.update(repo, ctx.node(), True, True, False,
2823 2823 ctx.p1().node())
2824 2824 finally:
2825 2825 repo.ui.setconfig('ui', 'forcemerge', '')
2826 2826 # report any conflicts
2827 2827 if stats and stats[3] > 0:
2828 2828 # write out state for --continue
2829 2829 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2830 2830 repo.opener.write('graftstate', ''.join(nodelines))
2831 2831 raise util.Abort(
2832 2832 _("unresolved conflicts, can't continue"),
2833 2833 hint=_('use hg resolve and hg graft --continue'))
2834 2834 else:
2835 2835 cont = False
2836 2836
2837 2837 # drop the second merge parent
2838 2838 repo.setparents(current.node(), nullid)
2839 2839 repo.dirstate.write()
2840 2840 # fix up dirstate for copies and renames
2841 2841 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2842 2842
2843 2843 # commit
2844 2844 source = ctx.extra().get('source')
2845 2845 if not source:
2846 2846 source = ctx.hex()
2847 2847 extra = {'source': source}
2848 2848 user = ctx.user()
2849 2849 if opts.get('user'):
2850 2850 user = opts['user']
2851 2851 date = ctx.date()
2852 2852 if opts.get('date'):
2853 2853 date = opts['date']
2854 2854 message = ctx.description()
2855 2855 if opts.get('log'):
2856 2856 message += '\n(grafted from %s)' % ctx.hex()
2857 2857 node = repo.commit(text=message, user=user,
2858 2858 date=date, extra=extra, editor=editor)
2859 2859 if node is None:
2860 2860 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2861 2861 finally:
2862 2862 wlock.release()
2863 2863
2864 2864 # remove state when we complete successfully
2865 2865 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2866 2866 util.unlinkpath(repo.join('graftstate'))
2867 2867
2868 2868 return 0
2869 2869
2870 2870 @command('grep',
2871 2871 [('0', 'print0', None, _('end fields with NUL')),
2872 2872 ('', 'all', None, _('print all revisions that match')),
2873 2873 ('a', 'text', None, _('treat all files as text')),
2874 2874 ('f', 'follow', None,
2875 2875 _('follow changeset history,'
2876 2876 ' or file history across copies and renames')),
2877 2877 ('i', 'ignore-case', None, _('ignore case when matching')),
2878 2878 ('l', 'files-with-matches', None,
2879 2879 _('print only filenames and revisions that match')),
2880 2880 ('n', 'line-number', None, _('print matching line numbers')),
2881 2881 ('r', 'rev', [],
2882 2882 _('only search files changed within revision range'), _('REV')),
2883 2883 ('u', 'user', None, _('list the author (long with -v)')),
2884 2884 ('d', 'date', None, _('list the date (short with -q)')),
2885 2885 ] + walkopts,
2886 2886 _('[OPTION]... PATTERN [FILE]...'))
2887 2887 def grep(ui, repo, pattern, *pats, **opts):
2888 2888 """search for a pattern in specified files and revisions
2889 2889
2890 2890 Search revisions of files for a regular expression.
2891 2891
2892 2892 This command behaves differently than Unix grep. It only accepts
2893 2893 Python/Perl regexps. It searches repository history, not the
2894 2894 working directory. It always prints the revision number in which a
2895 2895 match appears.
2896 2896
2897 2897 By default, grep only prints output for the first revision of a
2898 2898 file in which it finds a match. To get it to print every revision
2899 2899 that contains a change in match status ("-" for a match that
2900 2900 becomes a non-match, or "+" for a non-match that becomes a match),
2901 2901 use the --all flag.
2902 2902
2903 2903 Returns 0 if a match is found, 1 otherwise.
2904 2904 """
2905 2905 reflags = re.M
2906 2906 if opts.get('ignore_case'):
2907 2907 reflags |= re.I
2908 2908 try:
2909 2909 regexp = re.compile(pattern, reflags)
2910 2910 except re.error, inst:
2911 2911 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2912 2912 return 1
2913 2913 sep, eol = ':', '\n'
2914 2914 if opts.get('print0'):
2915 2915 sep = eol = '\0'
2916 2916
2917 2917 getfile = util.lrucachefunc(repo.file)
2918 2918
2919 2919 def matchlines(body):
2920 2920 begin = 0
2921 2921 linenum = 0
2922 2922 while True:
2923 2923 match = regexp.search(body, begin)
2924 2924 if not match:
2925 2925 break
2926 2926 mstart, mend = match.span()
2927 2927 linenum += body.count('\n', begin, mstart) + 1
2928 2928 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2929 2929 begin = body.find('\n', mend) + 1 or len(body) + 1
2930 2930 lend = begin - 1
2931 2931 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2932 2932
2933 2933 class linestate(object):
2934 2934 def __init__(self, line, linenum, colstart, colend):
2935 2935 self.line = line
2936 2936 self.linenum = linenum
2937 2937 self.colstart = colstart
2938 2938 self.colend = colend
2939 2939
2940 2940 def __hash__(self):
2941 2941 return hash((self.linenum, self.line))
2942 2942
2943 2943 def __eq__(self, other):
2944 2944 return self.line == other.line
2945 2945
2946 2946 matches = {}
2947 2947 copies = {}
2948 2948 def grepbody(fn, rev, body):
2949 2949 matches[rev].setdefault(fn, [])
2950 2950 m = matches[rev][fn]
2951 2951 for lnum, cstart, cend, line in matchlines(body):
2952 2952 s = linestate(line, lnum, cstart, cend)
2953 2953 m.append(s)
2954 2954
2955 2955 def difflinestates(a, b):
2956 2956 sm = difflib.SequenceMatcher(None, a, b)
2957 2957 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2958 2958 if tag == 'insert':
2959 2959 for i in xrange(blo, bhi):
2960 2960 yield ('+', b[i])
2961 2961 elif tag == 'delete':
2962 2962 for i in xrange(alo, ahi):
2963 2963 yield ('-', a[i])
2964 2964 elif tag == 'replace':
2965 2965 for i in xrange(alo, ahi):
2966 2966 yield ('-', a[i])
2967 2967 for i in xrange(blo, bhi):
2968 2968 yield ('+', b[i])
2969 2969
2970 2970 def display(fn, ctx, pstates, states):
2971 2971 rev = ctx.rev()
2972 2972 datefunc = ui.quiet and util.shortdate or util.datestr
2973 2973 found = False
2974 2974 filerevmatches = {}
2975 2975 def binary():
2976 2976 flog = getfile(fn)
2977 2977 return util.binary(flog.read(ctx.filenode(fn)))
2978 2978
2979 2979 if opts.get('all'):
2980 2980 iter = difflinestates(pstates, states)
2981 2981 else:
2982 2982 iter = [('', l) for l in states]
2983 2983 for change, l in iter:
2984 2984 cols = [fn, str(rev)]
2985 2985 before, match, after = None, None, None
2986 2986 if opts.get('line_number'):
2987 2987 cols.append(str(l.linenum))
2988 2988 if opts.get('all'):
2989 2989 cols.append(change)
2990 2990 if opts.get('user'):
2991 2991 cols.append(ui.shortuser(ctx.user()))
2992 2992 if opts.get('date'):
2993 2993 cols.append(datefunc(ctx.date()))
2994 2994 if opts.get('files_with_matches'):
2995 2995 c = (fn, rev)
2996 2996 if c in filerevmatches:
2997 2997 continue
2998 2998 filerevmatches[c] = 1
2999 2999 else:
3000 3000 before = l.line[:l.colstart]
3001 3001 match = l.line[l.colstart:l.colend]
3002 3002 after = l.line[l.colend:]
3003 3003 ui.write(sep.join(cols))
3004 3004 if before is not None:
3005 3005 if not opts.get('text') and binary():
3006 3006 ui.write(sep + " Binary file matches")
3007 3007 else:
3008 3008 ui.write(sep + before)
3009 3009 ui.write(match, label='grep.match')
3010 3010 ui.write(after)
3011 3011 ui.write(eol)
3012 3012 found = True
3013 3013 return found
3014 3014
3015 3015 skip = {}
3016 3016 revfiles = {}
3017 3017 matchfn = scmutil.match(repo[None], pats, opts)
3018 3018 found = False
3019 3019 follow = opts.get('follow')
3020 3020
3021 3021 def prep(ctx, fns):
3022 3022 rev = ctx.rev()
3023 3023 pctx = ctx.p1()
3024 3024 parent = pctx.rev()
3025 3025 matches.setdefault(rev, {})
3026 3026 matches.setdefault(parent, {})
3027 3027 files = revfiles.setdefault(rev, [])
3028 3028 for fn in fns:
3029 3029 flog = getfile(fn)
3030 3030 try:
3031 3031 fnode = ctx.filenode(fn)
3032 3032 except error.LookupError:
3033 3033 continue
3034 3034
3035 3035 copied = flog.renamed(fnode)
3036 3036 copy = follow and copied and copied[0]
3037 3037 if copy:
3038 3038 copies.setdefault(rev, {})[fn] = copy
3039 3039 if fn in skip:
3040 3040 if copy:
3041 3041 skip[copy] = True
3042 3042 continue
3043 3043 files.append(fn)
3044 3044
3045 3045 if fn not in matches[rev]:
3046 3046 grepbody(fn, rev, flog.read(fnode))
3047 3047
3048 3048 pfn = copy or fn
3049 3049 if pfn not in matches[parent]:
3050 3050 try:
3051 3051 fnode = pctx.filenode(pfn)
3052 3052 grepbody(pfn, parent, flog.read(fnode))
3053 3053 except error.LookupError:
3054 3054 pass
3055 3055
3056 3056 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3057 3057 rev = ctx.rev()
3058 3058 parent = ctx.p1().rev()
3059 3059 for fn in sorted(revfiles.get(rev, [])):
3060 3060 states = matches[rev][fn]
3061 3061 copy = copies.get(rev, {}).get(fn)
3062 3062 if fn in skip:
3063 3063 if copy:
3064 3064 skip[copy] = True
3065 3065 continue
3066 3066 pstates = matches.get(parent, {}).get(copy or fn, [])
3067 3067 if pstates or states:
3068 3068 r = display(fn, ctx, pstates, states)
3069 3069 found = found or r
3070 3070 if r and not opts.get('all'):
3071 3071 skip[fn] = True
3072 3072 if copy:
3073 3073 skip[copy] = True
3074 3074 del matches[rev]
3075 3075 del revfiles[rev]
3076 3076
3077 3077 return not found
3078 3078
3079 3079 @command('heads',
3080 3080 [('r', 'rev', '',
3081 3081 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3082 3082 ('t', 'topo', False, _('show topological heads only')),
3083 3083 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3084 3084 ('c', 'closed', False, _('show normal and closed branch heads')),
3085 3085 ] + templateopts,
3086 3086 _('[-ct] [-r STARTREV] [REV]...'))
3087 3087 def heads(ui, repo, *branchrevs, **opts):
3088 3088 """show current repository heads or show branch heads
3089 3089
3090 3090 With no arguments, show all repository branch heads.
3091 3091
3092 3092 Repository "heads" are changesets with no child changesets. They are
3093 3093 where development generally takes place and are the usual targets
3094 3094 for update and merge operations. Branch heads are changesets that have
3095 3095 no child changeset on the same branch.
3096 3096
3097 3097 If one or more REVs are given, only branch heads on the branches
3098 3098 associated with the specified changesets are shown. This means
3099 3099 that you can use :hg:`heads foo` to see the heads on a branch
3100 3100 named ``foo``.
3101 3101
3102 3102 If -c/--closed is specified, also show branch heads marked closed
3103 3103 (see :hg:`commit --close-branch`).
3104 3104
3105 3105 If STARTREV is specified, only those heads that are descendants of
3106 3106 STARTREV will be displayed.
3107 3107
3108 3108 If -t/--topo is specified, named branch mechanics will be ignored and only
3109 3109 changesets without children will be shown.
3110 3110
3111 3111 Returns 0 if matching heads are found, 1 if not.
3112 3112 """
3113 3113
3114 3114 start = None
3115 3115 if 'rev' in opts:
3116 3116 start = scmutil.revsingle(repo, opts['rev'], None).node()
3117 3117
3118 3118 if opts.get('topo'):
3119 3119 heads = [repo[h] for h in repo.heads(start)]
3120 3120 else:
3121 3121 heads = []
3122 3122 for branch in repo.branchmap():
3123 3123 heads += repo.branchheads(branch, start, opts.get('closed'))
3124 3124 heads = [repo[h] for h in heads]
3125 3125
3126 3126 if branchrevs:
3127 3127 branches = set(repo[br].branch() for br in branchrevs)
3128 3128 heads = [h for h in heads if h.branch() in branches]
3129 3129
3130 3130 if opts.get('active') and branchrevs:
3131 3131 dagheads = repo.heads(start)
3132 3132 heads = [h for h in heads if h.node() in dagheads]
3133 3133
3134 3134 if branchrevs:
3135 3135 haveheads = set(h.branch() for h in heads)
3136 3136 if branches - haveheads:
3137 3137 headless = ', '.join(b for b in branches - haveheads)
3138 3138 msg = _('no open branch heads found on branches %s')
3139 3139 if opts.get('rev'):
3140 3140 msg += _(' (started at %s)') % opts['rev']
3141 3141 ui.warn((msg + '\n') % headless)
3142 3142
3143 3143 if not heads:
3144 3144 return 1
3145 3145
3146 3146 heads = sorted(heads, key=lambda x: -x.rev())
3147 3147 displayer = cmdutil.show_changeset(ui, repo, opts)
3148 3148 for ctx in heads:
3149 3149 displayer.show(ctx)
3150 3150 displayer.close()
3151 3151
3152 3152 @command('help',
3153 3153 [('e', 'extension', None, _('show only help for extensions')),
3154 3154 ('c', 'command', None, _('show only help for commands')),
3155 3155 ('k', 'keyword', '', _('show topics matching keyword')),
3156 3156 ],
3157 3157 _('[-ec] [TOPIC]'))
3158 3158 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3159 3159 """show help for a given topic or a help overview
3160 3160
3161 3161 With no arguments, print a list of commands with short help messages.
3162 3162
3163 3163 Given a topic, extension, or command name, print help for that
3164 3164 topic.
3165 3165
3166 3166 Returns 0 if successful.
3167 3167 """
3168 3168
3169 3169 textwidth = min(ui.termwidth(), 80) - 2
3170 3170
3171 3171 def helpcmd(name):
3172 3172 try:
3173 3173 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3174 3174 except error.AmbiguousCommand, inst:
3175 3175 # py3k fix: except vars can't be used outside the scope of the
3176 3176 # except block, nor can be used inside a lambda. python issue4617
3177 3177 prefix = inst.args[0]
3178 3178 select = lambda c: c.lstrip('^').startswith(prefix)
3179 3179 rst = helplist(select)
3180 3180 return rst
3181 3181
3182 3182 rst = []
3183 3183
3184 3184 # check if it's an invalid alias and display its error if it is
3185 3185 if getattr(entry[0], 'badalias', False):
3186 3186 if not unknowncmd:
3187 3187 ui.pushbuffer()
3188 3188 entry[0](ui)
3189 3189 rst.append(ui.popbuffer())
3190 3190 return rst
3191 3191
3192 3192 # synopsis
3193 3193 if len(entry) > 2:
3194 3194 if entry[2].startswith('hg'):
3195 3195 rst.append("%s\n" % entry[2])
3196 3196 else:
3197 3197 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3198 3198 else:
3199 3199 rst.append('hg %s\n' % aliases[0])
3200 3200 # aliases
3201 3201 if full and not ui.quiet and len(aliases) > 1:
3202 3202 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3203 3203 rst.append('\n')
3204 3204
3205 3205 # description
3206 3206 doc = gettext(entry[0].__doc__)
3207 3207 if not doc:
3208 3208 doc = _("(no help text available)")
3209 3209 if util.safehasattr(entry[0], 'definition'): # aliased command
3210 3210 if entry[0].definition.startswith('!'): # shell alias
3211 3211 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3212 3212 else:
3213 3213 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3214 3214 doc = doc.splitlines(True)
3215 3215 if ui.quiet or not full:
3216 3216 rst.append(doc[0])
3217 3217 else:
3218 3218 rst.extend(doc)
3219 3219 rst.append('\n')
3220 3220
3221 3221 # check if this command shadows a non-trivial (multi-line)
3222 3222 # extension help text
3223 3223 try:
3224 3224 mod = extensions.find(name)
3225 3225 doc = gettext(mod.__doc__) or ''
3226 3226 if '\n' in doc.strip():
3227 3227 msg = _('use "hg help -e %s" to show help for '
3228 3228 'the %s extension') % (name, name)
3229 3229 rst.append('\n%s\n' % msg)
3230 3230 except KeyError:
3231 3231 pass
3232 3232
3233 3233 # options
3234 3234 if not ui.quiet and entry[1]:
3235 3235 rst.append('\n%s\n\n' % _("options:"))
3236 3236 rst.append(help.optrst(entry[1], ui.verbose))
3237 3237
3238 3238 if ui.verbose:
3239 3239 rst.append('\n%s\n\n' % _("global options:"))
3240 3240 rst.append(help.optrst(globalopts, ui.verbose))
3241 3241
3242 3242 if not ui.verbose:
3243 3243 if not full:
3244 3244 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3245 3245 % name)
3246 3246 elif not ui.quiet:
3247 3247 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3248 3248 % name)
3249 3249 return rst
3250 3250
3251 3251
3252 3252 def helplist(select=None):
3253 3253 # list of commands
3254 3254 if name == "shortlist":
3255 3255 header = _('basic commands:\n\n')
3256 3256 else:
3257 3257 header = _('list of commands:\n\n')
3258 3258
3259 3259 h = {}
3260 3260 cmds = {}
3261 3261 for c, e in table.iteritems():
3262 3262 f = c.split("|", 1)[0]
3263 3263 if select and not select(f):
3264 3264 continue
3265 3265 if (not select and name != 'shortlist' and
3266 3266 e[0].__module__ != __name__):
3267 3267 continue
3268 3268 if name == "shortlist" and not f.startswith("^"):
3269 3269 continue
3270 3270 f = f.lstrip("^")
3271 3271 if not ui.debugflag and f.startswith("debug"):
3272 3272 continue
3273 3273 doc = e[0].__doc__
3274 3274 if doc and 'DEPRECATED' in doc and not ui.verbose:
3275 3275 continue
3276 3276 doc = gettext(doc)
3277 3277 if not doc:
3278 3278 doc = _("(no help text available)")
3279 3279 h[f] = doc.splitlines()[0].rstrip()
3280 3280 cmds[f] = c.lstrip("^")
3281 3281
3282 3282 rst = []
3283 3283 if not h:
3284 3284 if not ui.quiet:
3285 3285 rst.append(_('no commands defined\n'))
3286 3286 return rst
3287 3287
3288 3288 if not ui.quiet:
3289 3289 rst.append(header)
3290 3290 fns = sorted(h)
3291 3291 for f in fns:
3292 3292 if ui.verbose:
3293 3293 commands = cmds[f].replace("|",", ")
3294 3294 rst.append(" :%s: %s\n" % (commands, h[f]))
3295 3295 else:
3296 3296 rst.append(' :%s: %s\n' % (f, h[f]))
3297 3297
3298 3298 if not name:
3299 3299 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3300 3300 if exts:
3301 3301 rst.append('\n')
3302 3302 rst.extend(exts)
3303 3303
3304 3304 rst.append(_("\nadditional help topics:\n\n"))
3305 3305 topics = []
3306 3306 for names, header, doc in help.helptable:
3307 3307 topics.append((names[0], header))
3308 3308 for t, desc in topics:
3309 3309 rst.append(" :%s: %s\n" % (t, desc))
3310 3310
3311 3311 optlist = []
3312 3312 if not ui.quiet:
3313 3313 if ui.verbose:
3314 3314 optlist.append((_("global options:"), globalopts))
3315 3315 if name == 'shortlist':
3316 3316 optlist.append((_('use "hg help" for the full list '
3317 3317 'of commands'), ()))
3318 3318 else:
3319 3319 if name == 'shortlist':
3320 3320 msg = _('use "hg help" for the full list of commands '
3321 3321 'or "hg -v" for details')
3322 3322 elif name and not full:
3323 3323 msg = _('use "hg help %s" to show the full help '
3324 3324 'text') % name
3325 3325 else:
3326 3326 msg = _('use "hg -v help%s" to show builtin aliases and '
3327 3327 'global options') % (name and " " + name or "")
3328 3328 optlist.append((msg, ()))
3329 3329
3330 3330 if optlist:
3331 3331 for title, options in optlist:
3332 3332 rst.append('\n%s\n' % title)
3333 3333 if options:
3334 3334 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3335 3335 return rst
3336 3336
3337 3337 def helptopic(name):
3338 3338 for names, header, doc in help.helptable:
3339 3339 if name in names:
3340 3340 break
3341 3341 else:
3342 3342 raise error.UnknownCommand(name)
3343 3343
3344 3344 rst = ["%s\n\n" % header]
3345 3345 # description
3346 3346 if not doc:
3347 3347 rst.append(" %s\n" % _("(no help text available)"))
3348 3348 if util.safehasattr(doc, '__call__'):
3349 3349 rst += [" %s\n" % l for l in doc().splitlines()]
3350 3350
3351 3351 try:
3352 3352 cmdutil.findcmd(name, table)
3353 3353 rst.append(_('\nuse "hg help -c %s" to see help for '
3354 3354 'the %s command\n') % (name, name))
3355 3355 except error.UnknownCommand:
3356 3356 pass
3357 3357 return rst
3358 3358
3359 3359 def helpext(name):
3360 3360 try:
3361 3361 mod = extensions.find(name)
3362 3362 doc = gettext(mod.__doc__) or _('no help text available')
3363 3363 except KeyError:
3364 3364 mod = None
3365 3365 doc = extensions.disabledext(name)
3366 3366 if not doc:
3367 3367 raise error.UnknownCommand(name)
3368 3368
3369 3369 if '\n' not in doc:
3370 3370 head, tail = doc, ""
3371 3371 else:
3372 3372 head, tail = doc.split('\n', 1)
3373 3373 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3374 3374 if tail:
3375 3375 rst.extend(tail.splitlines(True))
3376 3376 rst.append('\n')
3377 3377
3378 3378 if mod:
3379 3379 try:
3380 3380 ct = mod.cmdtable
3381 3381 except AttributeError:
3382 3382 ct = {}
3383 3383 modcmds = set([c.split('|', 1)[0] for c in ct])
3384 3384 rst.extend(helplist(modcmds.__contains__))
3385 3385 else:
3386 3386 rst.append(_('use "hg help extensions" for information on enabling '
3387 3387 'extensions\n'))
3388 3388 return rst
3389 3389
3390 3390 def helpextcmd(name):
3391 3391 cmd, ext, mod = extensions.disabledcmd(ui, name,
3392 3392 ui.configbool('ui', 'strict'))
3393 3393 doc = gettext(mod.__doc__).splitlines()[0]
3394 3394
3395 3395 rst = help.listexts(_("'%s' is provided by the following "
3396 3396 "extension:") % cmd, {ext: doc}, indent=4)
3397 3397 rst.append('\n')
3398 3398 rst.append(_('use "hg help extensions" for information on enabling '
3399 3399 'extensions\n'))
3400 3400 return rst
3401 3401
3402 3402
3403 3403 rst = []
3404 3404 kw = opts.get('keyword')
3405 3405 if kw:
3406 3406 matches = help.topicmatch(kw)
3407 3407 for t, title in (('topics', _('Topics')),
3408 3408 ('commands', _('Commands')),
3409 3409 ('extensions', _('Extensions')),
3410 3410 ('extensioncommands', _('Extension Commands'))):
3411 3411 if matches[t]:
3412 3412 rst.append('%s:\n\n' % title)
3413 3413 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3414 3414 rst.append('\n')
3415 3415 elif name and name != 'shortlist':
3416 3416 i = None
3417 3417 if unknowncmd:
3418 3418 queries = (helpextcmd,)
3419 3419 elif opts.get('extension'):
3420 3420 queries = (helpext,)
3421 3421 elif opts.get('command'):
3422 3422 queries = (helpcmd,)
3423 3423 else:
3424 3424 queries = (helptopic, helpcmd, helpext, helpextcmd)
3425 3425 for f in queries:
3426 3426 try:
3427 3427 rst = f(name)
3428 3428 i = None
3429 3429 break
3430 3430 except error.UnknownCommand, inst:
3431 3431 i = inst
3432 3432 if i:
3433 3433 raise i
3434 3434 else:
3435 3435 # program name
3436 3436 if not ui.quiet:
3437 3437 rst = [_("Mercurial Distributed SCM\n"), '\n']
3438 3438 rst.extend(helplist())
3439 3439
3440 3440 keep = ui.verbose and ['verbose'] or []
3441 3441 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3442 3442 ui.write(formatted)
3443 3443
3444 3444
3445 3445 @command('identify|id',
3446 3446 [('r', 'rev', '',
3447 3447 _('identify the specified revision'), _('REV')),
3448 3448 ('n', 'num', None, _('show local revision number')),
3449 3449 ('i', 'id', None, _('show global revision id')),
3450 3450 ('b', 'branch', None, _('show branch')),
3451 3451 ('t', 'tags', None, _('show tags')),
3452 3452 ('B', 'bookmarks', None, _('show bookmarks')),
3453 3453 ] + remoteopts,
3454 3454 _('[-nibtB] [-r REV] [SOURCE]'))
3455 3455 def identify(ui, repo, source=None, rev=None,
3456 3456 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3457 3457 """identify the working copy or specified revision
3458 3458
3459 3459 Print a summary identifying the repository state at REV using one or
3460 3460 two parent hash identifiers, followed by a "+" if the working
3461 3461 directory has uncommitted changes, the branch name (if not default),
3462 3462 a list of tags, and a list of bookmarks.
3463 3463
3464 3464 When REV is not given, print a summary of the current state of the
3465 3465 repository.
3466 3466
3467 3467 Specifying a path to a repository root or Mercurial bundle will
3468 3468 cause lookup to operate on that repository/bundle.
3469 3469
3470 3470 .. container:: verbose
3471 3471
3472 3472 Examples:
3473 3473
3474 3474 - generate a build identifier for the working directory::
3475 3475
3476 3476 hg id --id > build-id.dat
3477 3477
3478 3478 - find the revision corresponding to a tag::
3479 3479
3480 3480 hg id -n -r 1.3
3481 3481
3482 3482 - check the most recent revision of a remote repository::
3483 3483
3484 3484 hg id -r tip http://selenic.com/hg/
3485 3485
3486 3486 Returns 0 if successful.
3487 3487 """
3488 3488
3489 3489 if not repo and not source:
3490 3490 raise util.Abort(_("there is no Mercurial repository here "
3491 3491 "(.hg not found)"))
3492 3492
3493 3493 hexfunc = ui.debugflag and hex or short
3494 3494 default = not (num or id or branch or tags or bookmarks)
3495 3495 output = []
3496 3496 revs = []
3497 3497
3498 3498 if source:
3499 3499 source, branches = hg.parseurl(ui.expandpath(source))
3500 3500 peer = hg.peer(ui, opts, source)
3501 3501 repo = peer.local()
3502 3502 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3503 3503
3504 3504 if not repo:
3505 3505 if num or branch or tags:
3506 3506 raise util.Abort(
3507 3507 _("can't query remote revision number, branch, or tags"))
3508 3508 if not rev and revs:
3509 3509 rev = revs[0]
3510 3510 if not rev:
3511 3511 rev = "tip"
3512 3512
3513 3513 remoterev = peer.lookup(rev)
3514 3514 if default or id:
3515 3515 output = [hexfunc(remoterev)]
3516 3516
3517 3517 def getbms():
3518 3518 bms = []
3519 3519
3520 3520 if 'bookmarks' in peer.listkeys('namespaces'):
3521 3521 hexremoterev = hex(remoterev)
3522 3522 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3523 3523 if bmr == hexremoterev]
3524 3524
3525 3525 return bms
3526 3526
3527 3527 if bookmarks:
3528 3528 output.extend(getbms())
3529 3529 elif default and not ui.quiet:
3530 3530 # multiple bookmarks for a single parent separated by '/'
3531 3531 bm = '/'.join(getbms())
3532 3532 if bm:
3533 3533 output.append(bm)
3534 3534 else:
3535 3535 if not rev:
3536 3536 ctx = repo[None]
3537 3537 parents = ctx.parents()
3538 3538 changed = ""
3539 3539 if default or id or num:
3540 3540 if (util.any(repo.status())
3541 3541 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3542 3542 changed = '+'
3543 3543 if default or id:
3544 3544 output = ["%s%s" %
3545 3545 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3546 3546 if num:
3547 3547 output.append("%s%s" %
3548 3548 ('+'.join([str(p.rev()) for p in parents]), changed))
3549 3549 else:
3550 3550 ctx = scmutil.revsingle(repo, rev)
3551 3551 if default or id:
3552 3552 output = [hexfunc(ctx.node())]
3553 3553 if num:
3554 3554 output.append(str(ctx.rev()))
3555 3555
3556 3556 if default and not ui.quiet:
3557 3557 b = ctx.branch()
3558 3558 if b != 'default':
3559 3559 output.append("(%s)" % b)
3560 3560
3561 3561 # multiple tags for a single parent separated by '/'
3562 3562 t = '/'.join(ctx.tags())
3563 3563 if t:
3564 3564 output.append(t)
3565 3565
3566 3566 # multiple bookmarks for a single parent separated by '/'
3567 3567 bm = '/'.join(ctx.bookmarks())
3568 3568 if bm:
3569 3569 output.append(bm)
3570 3570 else:
3571 3571 if branch:
3572 3572 output.append(ctx.branch())
3573 3573
3574 3574 if tags:
3575 3575 output.extend(ctx.tags())
3576 3576
3577 3577 if bookmarks:
3578 3578 output.extend(ctx.bookmarks())
3579 3579
3580 3580 ui.write("%s\n" % ' '.join(output))
3581 3581
3582 3582 @command('import|patch',
3583 3583 [('p', 'strip', 1,
3584 3584 _('directory strip option for patch. This has the same '
3585 3585 'meaning as the corresponding patch option'), _('NUM')),
3586 3586 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3587 3587 ('e', 'edit', False, _('invoke editor on commit messages')),
3588 3588 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3589 3589 ('', 'no-commit', None,
3590 3590 _("don't commit, just update the working directory")),
3591 3591 ('', 'bypass', None,
3592 3592 _("apply patch without touching the working directory")),
3593 3593 ('', 'exact', None,
3594 3594 _('apply patch to the nodes from which it was generated')),
3595 3595 ('', 'import-branch', None,
3596 3596 _('use any branch information in patch (implied by --exact)'))] +
3597 3597 commitopts + commitopts2 + similarityopts,
3598 3598 _('[OPTION]... PATCH...'))
3599 3599 def import_(ui, repo, patch1=None, *patches, **opts):
3600 3600 """import an ordered set of patches
3601 3601
3602 3602 Import a list of patches and commit them individually (unless
3603 3603 --no-commit is specified).
3604 3604
3605 3605 If there are outstanding changes in the working directory, import
3606 3606 will abort unless given the -f/--force flag.
3607 3607
3608 3608 You can import a patch straight from a mail message. Even patches
3609 3609 as attachments work (to use the body part, it must have type
3610 3610 text/plain or text/x-patch). From and Subject headers of email
3611 3611 message are used as default committer and commit message. All
3612 3612 text/plain body parts before first diff are added to commit
3613 3613 message.
3614 3614
3615 3615 If the imported patch was generated by :hg:`export`, user and
3616 3616 description from patch override values from message headers and
3617 3617 body. Values given on command line with -m/--message and -u/--user
3618 3618 override these.
3619 3619
3620 3620 If --exact is specified, import will set the working directory to
3621 3621 the parent of each patch before applying it, and will abort if the
3622 3622 resulting changeset has a different ID than the one recorded in
3623 3623 the patch. This may happen due to character set problems or other
3624 3624 deficiencies in the text patch format.
3625 3625
3626 3626 Use --bypass to apply and commit patches directly to the
3627 3627 repository, not touching the working directory. Without --exact,
3628 3628 patches will be applied on top of the working directory parent
3629 3629 revision.
3630 3630
3631 3631 With -s/--similarity, hg will attempt to discover renames and
3632 3632 copies in the patch in the same way as :hg:`addremove`.
3633 3633
3634 3634 To read a patch from standard input, use "-" as the patch name. If
3635 3635 a URL is specified, the patch will be downloaded from it.
3636 3636 See :hg:`help dates` for a list of formats valid for -d/--date.
3637 3637
3638 3638 .. container:: verbose
3639 3639
3640 3640 Examples:
3641 3641
3642 3642 - import a traditional patch from a website and detect renames::
3643 3643
3644 3644 hg import -s 80 http://example.com/bugfix.patch
3645 3645
3646 3646 - import a changeset from an hgweb server::
3647 3647
3648 3648 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3649 3649
3650 3650 - import all the patches in an Unix-style mbox::
3651 3651
3652 3652 hg import incoming-patches.mbox
3653 3653
3654 3654 - attempt to exactly restore an exported changeset (not always
3655 3655 possible)::
3656 3656
3657 3657 hg import --exact proposed-fix.patch
3658 3658
3659 3659 Returns 0 on success.
3660 3660 """
3661 3661
3662 3662 if not patch1:
3663 3663 raise util.Abort(_('need at least one patch to import'))
3664 3664
3665 3665 patches = (patch1,) + patches
3666 3666
3667 3667 date = opts.get('date')
3668 3668 if date:
3669 3669 opts['date'] = util.parsedate(date)
3670 3670
3671 3671 editor = cmdutil.commiteditor
3672 3672 if opts.get('edit'):
3673 3673 editor = cmdutil.commitforceeditor
3674 3674
3675 3675 update = not opts.get('bypass')
3676 3676 if not update and opts.get('no_commit'):
3677 3677 raise util.Abort(_('cannot use --no-commit with --bypass'))
3678 3678 try:
3679 3679 sim = float(opts.get('similarity') or 0)
3680 3680 except ValueError:
3681 3681 raise util.Abort(_('similarity must be a number'))
3682 3682 if sim < 0 or sim > 100:
3683 3683 raise util.Abort(_('similarity must be between 0 and 100'))
3684 3684 if sim and not update:
3685 3685 raise util.Abort(_('cannot use --similarity with --bypass'))
3686 3686
3687 3687 if (opts.get('exact') or not opts.get('force')) and update:
3688 3688 cmdutil.bailifchanged(repo)
3689 3689
3690 3690 base = opts["base"]
3691 3691 strip = opts["strip"]
3692 3692 wlock = lock = tr = None
3693 3693 msgs = []
3694 3694
3695 3695 def checkexact(repo, n, nodeid):
3696 3696 if opts.get('exact') and hex(n) != nodeid:
3697 3697 repo.rollback()
3698 3698 raise util.Abort(_('patch is damaged or loses information'))
3699 3699
3700 3700 def tryone(ui, hunk, parents):
3701 3701 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3702 3702 patch.extract(ui, hunk)
3703 3703
3704 3704 if not tmpname:
3705 3705 return (None, None)
3706 3706 msg = _('applied to working directory')
3707 3707
3708 3708 try:
3709 3709 cmdline_message = cmdutil.logmessage(ui, opts)
3710 3710 if cmdline_message:
3711 3711 # pickup the cmdline msg
3712 3712 message = cmdline_message
3713 3713 elif message:
3714 3714 # pickup the patch msg
3715 3715 message = message.strip()
3716 3716 else:
3717 3717 # launch the editor
3718 3718 message = None
3719 3719 ui.debug('message:\n%s\n' % message)
3720 3720
3721 3721 if len(parents) == 1:
3722 3722 parents.append(repo[nullid])
3723 3723 if opts.get('exact'):
3724 3724 if not nodeid or not p1:
3725 3725 raise util.Abort(_('not a Mercurial patch'))
3726 3726 p1 = repo[p1]
3727 3727 p2 = repo[p2 or nullid]
3728 3728 elif p2:
3729 3729 try:
3730 3730 p1 = repo[p1]
3731 3731 p2 = repo[p2]
3732 3732 # Without any options, consider p2 only if the
3733 3733 # patch is being applied on top of the recorded
3734 3734 # first parent.
3735 3735 if p1 != parents[0]:
3736 3736 p1 = parents[0]
3737 3737 p2 = repo[nullid]
3738 3738 except error.RepoError:
3739 3739 p1, p2 = parents
3740 3740 else:
3741 3741 p1, p2 = parents
3742 3742
3743 3743 n = None
3744 3744 if update:
3745 3745 if p1 != parents[0]:
3746 3746 hg.clean(repo, p1.node())
3747 3747 if p2 != parents[1]:
3748 3748 repo.setparents(p1.node(), p2.node())
3749 3749
3750 3750 if opts.get('exact') or opts.get('import_branch'):
3751 3751 repo.dirstate.setbranch(branch or 'default')
3752 3752
3753 3753 files = set()
3754 3754 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3755 3755 eolmode=None, similarity=sim / 100.0)
3756 3756 files = list(files)
3757 3757 if opts.get('no_commit'):
3758 3758 if message:
3759 3759 msgs.append(message)
3760 3760 else:
3761 3761 if opts.get('exact') or p2:
3762 3762 # If you got here, you either use --force and know what
3763 3763 # you are doing or used --exact or a merge patch while
3764 3764 # being updated to its first parent.
3765 3765 m = None
3766 3766 else:
3767 3767 m = scmutil.matchfiles(repo, files or [])
3768 3768 n = repo.commit(message, opts.get('user') or user,
3769 3769 opts.get('date') or date, match=m,
3770 3770 editor=editor)
3771 3771 checkexact(repo, n, nodeid)
3772 3772 else:
3773 3773 if opts.get('exact') or opts.get('import_branch'):
3774 3774 branch = branch or 'default'
3775 3775 else:
3776 3776 branch = p1.branch()
3777 3777 store = patch.filestore()
3778 3778 try:
3779 3779 files = set()
3780 3780 try:
3781 3781 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3782 3782 files, eolmode=None)
3783 3783 except patch.PatchError, e:
3784 3784 raise util.Abort(str(e))
3785 3785 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3786 3786 message,
3787 3787 opts.get('user') or user,
3788 3788 opts.get('date') or date,
3789 3789 branch, files, store,
3790 3790 editor=cmdutil.commiteditor)
3791 3791 repo.savecommitmessage(memctx.description())
3792 3792 n = memctx.commit()
3793 3793 checkexact(repo, n, nodeid)
3794 3794 finally:
3795 3795 store.close()
3796 3796 if n:
3797 3797 # i18n: refers to a short changeset id
3798 3798 msg = _('created %s') % short(n)
3799 3799 return (msg, n)
3800 3800 finally:
3801 3801 os.unlink(tmpname)
3802 3802
3803 3803 try:
3804 3804 try:
3805 3805 wlock = repo.wlock()
3806 3806 if not opts.get('no_commit'):
3807 3807 lock = repo.lock()
3808 3808 tr = repo.transaction('import')
3809 3809 parents = repo.parents()
3810 3810 for patchurl in patches:
3811 3811 if patchurl == '-':
3812 3812 ui.status(_('applying patch from stdin\n'))
3813 3813 patchfile = ui.fin
3814 3814 patchurl = 'stdin' # for error message
3815 3815 else:
3816 3816 patchurl = os.path.join(base, patchurl)
3817 3817 ui.status(_('applying %s\n') % patchurl)
3818 3818 patchfile = url.open(ui, patchurl)
3819 3819
3820 3820 haspatch = False
3821 3821 for hunk in patch.split(patchfile):
3822 3822 (msg, node) = tryone(ui, hunk, parents)
3823 3823 if msg:
3824 3824 haspatch = True
3825 3825 ui.note(msg + '\n')
3826 3826 if update or opts.get('exact'):
3827 3827 parents = repo.parents()
3828 3828 else:
3829 3829 parents = [repo[node]]
3830 3830
3831 3831 if not haspatch:
3832 3832 raise util.Abort(_('%s: no diffs found') % patchurl)
3833 3833
3834 3834 if tr:
3835 3835 tr.close()
3836 3836 if msgs:
3837 3837 repo.savecommitmessage('\n* * *\n'.join(msgs))
3838 3838 except: # re-raises
3839 3839 # wlock.release() indirectly calls dirstate.write(): since
3840 3840 # we're crashing, we do not want to change the working dir
3841 3841 # parent after all, so make sure it writes nothing
3842 3842 repo.dirstate.invalidate()
3843 3843 raise
3844 3844 finally:
3845 3845 if tr:
3846 3846 tr.release()
3847 3847 release(lock, wlock)
3848 3848
3849 3849 @command('incoming|in',
3850 3850 [('f', 'force', None,
3851 3851 _('run even if remote repository is unrelated')),
3852 3852 ('n', 'newest-first', None, _('show newest record first')),
3853 3853 ('', 'bundle', '',
3854 3854 _('file to store the bundles into'), _('FILE')),
3855 3855 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3856 3856 ('B', 'bookmarks', False, _("compare bookmarks")),
3857 3857 ('b', 'branch', [],
3858 3858 _('a specific branch you would like to pull'), _('BRANCH')),
3859 3859 ] + logopts + remoteopts + subrepoopts,
3860 3860 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3861 3861 def incoming(ui, repo, source="default", **opts):
3862 3862 """show new changesets found in source
3863 3863
3864 3864 Show new changesets found in the specified path/URL or the default
3865 3865 pull location. These are the changesets that would have been pulled
3866 3866 if a pull at the time you issued this command.
3867 3867
3868 3868 For remote repository, using --bundle avoids downloading the
3869 3869 changesets twice if the incoming is followed by a pull.
3870 3870
3871 3871 See pull for valid source format details.
3872 3872
3873 3873 Returns 0 if there are incoming changes, 1 otherwise.
3874 3874 """
3875 3875 if opts.get('graph'):
3876 3876 cmdutil.checkunsupportedgraphflags([], opts)
3877 3877 def display(other, chlist, displayer):
3878 3878 revdag = cmdutil.graphrevs(other, chlist, opts)
3879 3879 showparents = [ctx.node() for ctx in repo[None].parents()]
3880 3880 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3881 3881 graphmod.asciiedges)
3882 3882
3883 3883 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3884 3884 return 0
3885 3885
3886 3886 if opts.get('bundle') and opts.get('subrepos'):
3887 3887 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3888 3888
3889 3889 if opts.get('bookmarks'):
3890 3890 source, branches = hg.parseurl(ui.expandpath(source),
3891 3891 opts.get('branch'))
3892 3892 other = hg.peer(repo, opts, source)
3893 3893 if 'bookmarks' not in other.listkeys('namespaces'):
3894 3894 ui.warn(_("remote doesn't support bookmarks\n"))
3895 3895 return 0
3896 3896 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3897 3897 return bookmarks.diff(ui, repo, other)
3898 3898
3899 3899 repo._subtoppath = ui.expandpath(source)
3900 3900 try:
3901 3901 return hg.incoming(ui, repo, source, opts)
3902 3902 finally:
3903 3903 del repo._subtoppath
3904 3904
3905 3905
3906 3906 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3907 3907 def init(ui, dest=".", **opts):
3908 3908 """create a new repository in the given directory
3909 3909
3910 3910 Initialize a new repository in the given directory. If the given
3911 3911 directory does not exist, it will be created.
3912 3912
3913 3913 If no directory is given, the current directory is used.
3914 3914
3915 3915 It is possible to specify an ``ssh://`` URL as the destination.
3916 3916 See :hg:`help urls` for more information.
3917 3917
3918 3918 Returns 0 on success.
3919 3919 """
3920 3920 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3921 3921
3922 3922 @command('locate',
3923 3923 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3924 3924 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3925 3925 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3926 3926 ] + walkopts,
3927 3927 _('[OPTION]... [PATTERN]...'))
3928 3928 def locate(ui, repo, *pats, **opts):
3929 3929 """locate files matching specific patterns
3930 3930
3931 3931 Print files under Mercurial control in the working directory whose
3932 3932 names match the given patterns.
3933 3933
3934 3934 By default, this command searches all directories in the working
3935 3935 directory. To search just the current directory and its
3936 3936 subdirectories, use "--include .".
3937 3937
3938 3938 If no patterns are given to match, this command prints the names
3939 3939 of all files under Mercurial control in the working directory.
3940 3940
3941 3941 If you want to feed the output of this command into the "xargs"
3942 3942 command, use the -0 option to both this command and "xargs". This
3943 3943 will avoid the problem of "xargs" treating single filenames that
3944 3944 contain whitespace as multiple filenames.
3945 3945
3946 3946 Returns 0 if a match is found, 1 otherwise.
3947 3947 """
3948 3948 end = opts.get('print0') and '\0' or '\n'
3949 3949 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3950 3950
3951 3951 ret = 1
3952 3952 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3953 3953 m.bad = lambda x, y: False
3954 3954 for abs in repo[rev].walk(m):
3955 3955 if not rev and abs not in repo.dirstate:
3956 3956 continue
3957 3957 if opts.get('fullpath'):
3958 3958 ui.write(repo.wjoin(abs), end)
3959 3959 else:
3960 3960 ui.write(((pats and m.rel(abs)) or abs), end)
3961 3961 ret = 0
3962 3962
3963 3963 return ret
3964 3964
3965 3965 @command('^log|history',
3966 3966 [('f', 'follow', None,
3967 3967 _('follow changeset history, or file history across copies and renames')),
3968 3968 ('', 'follow-first', None,
3969 3969 _('only follow the first parent of merge changesets (DEPRECATED)')),
3970 3970 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3971 3971 ('C', 'copies', None, _('show copied files')),
3972 3972 ('k', 'keyword', [],
3973 3973 _('do case-insensitive search for a given text'), _('TEXT')),
3974 3974 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3975 3975 ('', 'removed', None, _('include revisions where files were removed')),
3976 3976 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3977 3977 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3978 3978 ('', 'only-branch', [],
3979 3979 _('show only changesets within the given named branch (DEPRECATED)'),
3980 3980 _('BRANCH')),
3981 3981 ('b', 'branch', [],
3982 3982 _('show changesets within the given named branch'), _('BRANCH')),
3983 3983 ('P', 'prune', [],
3984 3984 _('do not display revision or any of its ancestors'), _('REV')),
3985 3985 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3986 3986 ] + logopts + walkopts,
3987 3987 _('[OPTION]... [FILE]'))
3988 3988 def log(ui, repo, *pats, **opts):
3989 3989 """show revision history of entire repository or files
3990 3990
3991 3991 Print the revision history of the specified files or the entire
3992 3992 project.
3993 3993
3994 3994 If no revision range is specified, the default is ``tip:0`` unless
3995 3995 --follow is set, in which case the working directory parent is
3996 3996 used as the starting revision.
3997 3997
3998 3998 File history is shown without following rename or copy history of
3999 3999 files. Use -f/--follow with a filename to follow history across
4000 4000 renames and copies. --follow without a filename will only show
4001 4001 ancestors or descendants of the starting revision.
4002 4002
4003 4003 By default this command prints revision number and changeset id,
4004 4004 tags, non-trivial parents, user, date and time, and a summary for
4005 4005 each commit. When the -v/--verbose switch is used, the list of
4006 4006 changed files and full commit message are shown.
4007 4007
4008 4008 .. note::
4009 4009 log -p/--patch may generate unexpected diff output for merge
4010 4010 changesets, as it will only compare the merge changeset against
4011 4011 its first parent. Also, only files different from BOTH parents
4012 4012 will appear in files:.
4013 4013
4014 4014 .. note::
4015 4015 for performance reasons, log FILE may omit duplicate changes
4016 4016 made on branches and will not show deletions. To see all
4017 4017 changes including duplicates and deletions, use the --removed
4018 4018 switch.
4019 4019
4020 4020 .. container:: verbose
4021 4021
4022 4022 Some examples:
4023 4023
4024 4024 - changesets with full descriptions and file lists::
4025 4025
4026 4026 hg log -v
4027 4027
4028 4028 - changesets ancestral to the working directory::
4029 4029
4030 4030 hg log -f
4031 4031
4032 4032 - last 10 commits on the current branch::
4033 4033
4034 4034 hg log -l 10 -b .
4035 4035
4036 4036 - changesets showing all modifications of a file, including removals::
4037 4037
4038 4038 hg log --removed file.c
4039 4039
4040 4040 - all changesets that touch a directory, with diffs, excluding merges::
4041 4041
4042 4042 hg log -Mp lib/
4043 4043
4044 4044 - all revision numbers that match a keyword::
4045 4045
4046 4046 hg log -k bug --template "{rev}\\n"
4047 4047
4048 4048 - check if a given changeset is included is a tagged release::
4049 4049
4050 4050 hg log -r "a21ccf and ancestor(1.9)"
4051 4051
4052 4052 - find all changesets by some user in a date range::
4053 4053
4054 4054 hg log -k alice -d "may 2008 to jul 2008"
4055 4055
4056 4056 - summary of all changesets after the last tag::
4057 4057
4058 4058 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4059 4059
4060 4060 See :hg:`help dates` for a list of formats valid for -d/--date.
4061 4061
4062 4062 See :hg:`help revisions` and :hg:`help revsets` for more about
4063 4063 specifying revisions.
4064 4064
4065 4065 See :hg:`help templates` for more about pre-packaged styles and
4066 4066 specifying custom templates.
4067 4067
4068 4068 Returns 0 on success.
4069 4069 """
4070 4070 if opts.get('graph'):
4071 4071 return cmdutil.graphlog(ui, repo, *pats, **opts)
4072 4072
4073 4073 matchfn = scmutil.match(repo[None], pats, opts)
4074 4074 limit = cmdutil.loglimit(opts)
4075 4075 count = 0
4076 4076
4077 4077 getrenamed, endrev = None, None
4078 4078 if opts.get('copies'):
4079 4079 if opts.get('rev'):
4080 4080 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4081 4081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4082 4082
4083 4083 df = False
4084 4084 if opts.get("date"):
4085 4085 df = util.matchdate(opts["date"])
4086 4086
4087 4087 branches = opts.get('branch', []) + opts.get('only_branch', [])
4088 4088 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4089 4089
4090 4090 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4091 4091 def prep(ctx, fns):
4092 4092 rev = ctx.rev()
4093 4093 parents = [p for p in repo.changelog.parentrevs(rev)
4094 4094 if p != nullrev]
4095 4095 if opts.get('no_merges') and len(parents) == 2:
4096 4096 return
4097 4097 if opts.get('only_merges') and len(parents) != 2:
4098 4098 return
4099 4099 if opts.get('branch') and ctx.branch() not in opts['branch']:
4100 4100 return
4101 4101 if not opts.get('hidden') and ctx.hidden():
4102 4102 return
4103 4103 if df and not df(ctx.date()[0]):
4104 4104 return
4105 4105
4106 4106 lower = encoding.lower
4107 4107 if opts.get('user'):
4108 4108 luser = lower(ctx.user())
4109 4109 for k in [lower(x) for x in opts['user']]:
4110 4110 if (k in luser):
4111 4111 break
4112 4112 else:
4113 4113 return
4114 4114 if opts.get('keyword'):
4115 4115 luser = lower(ctx.user())
4116 4116 ldesc = lower(ctx.description())
4117 4117 lfiles = lower(" ".join(ctx.files()))
4118 4118 for k in [lower(x) for x in opts['keyword']]:
4119 4119 if (k in luser or k in ldesc or k in lfiles):
4120 4120 break
4121 4121 else:
4122 4122 return
4123 4123
4124 4124 copies = None
4125 4125 if getrenamed is not None and rev:
4126 4126 copies = []
4127 4127 for fn in ctx.files():
4128 4128 rename = getrenamed(fn, rev)
4129 4129 if rename:
4130 4130 copies.append((fn, rename[0]))
4131 4131
4132 4132 revmatchfn = None
4133 4133 if opts.get('patch') or opts.get('stat'):
4134 4134 if opts.get('follow') or opts.get('follow_first'):
4135 4135 # note: this might be wrong when following through merges
4136 4136 revmatchfn = scmutil.match(repo[None], fns, default='path')
4137 4137 else:
4138 4138 revmatchfn = matchfn
4139 4139
4140 4140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4141 4141
4142 4142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4143 4143 if count == limit:
4144 4144 break
4145 4145 if displayer.flush(ctx.rev()):
4146 4146 count += 1
4147 4147 displayer.close()
4148 4148
4149 4149 @command('manifest',
4150 4150 [('r', 'rev', '', _('revision to display'), _('REV')),
4151 4151 ('', 'all', False, _("list files from all revisions"))],
4152 4152 _('[-r REV]'))
4153 4153 def manifest(ui, repo, node=None, rev=None, **opts):
4154 4154 """output the current or given revision of the project manifest
4155 4155
4156 4156 Print a list of version controlled files for the given revision.
4157 4157 If no revision is given, the first parent of the working directory
4158 4158 is used, or the null revision if no revision is checked out.
4159 4159
4160 4160 With -v, print file permissions, symlink and executable bits.
4161 4161 With --debug, print file revision hashes.
4162 4162
4163 4163 If option --all is specified, the list of all files from all revisions
4164 4164 is printed. This includes deleted and renamed files.
4165 4165
4166 4166 Returns 0 on success.
4167 4167 """
4168 4168 if opts.get('all'):
4169 4169 if rev or node:
4170 4170 raise util.Abort(_("can't specify a revision with --all"))
4171 4171
4172 4172 res = []
4173 4173 prefix = "data/"
4174 4174 suffix = ".i"
4175 4175 plen = len(prefix)
4176 4176 slen = len(suffix)
4177 4177 lock = repo.lock()
4178 4178 try:
4179 4179 for fn, b, size in repo.store.datafiles():
4180 4180 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4181 4181 res.append(fn[plen:-slen])
4182 4182 finally:
4183 4183 lock.release()
4184 4184 for f in res:
4185 4185 ui.write("%s\n" % f)
4186 4186 return
4187 4187
4188 4188 if rev and node:
4189 4189 raise util.Abort(_("please specify just one revision"))
4190 4190
4191 4191 if not node:
4192 4192 node = rev
4193 4193
4194 4194 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4195 4195 ctx = scmutil.revsingle(repo, node)
4196 4196 for f in ctx:
4197 4197 if ui.debugflag:
4198 4198 ui.write("%40s " % hex(ctx.manifest()[f]))
4199 4199 if ui.verbose:
4200 4200 ui.write(decor[ctx.flags(f)])
4201 4201 ui.write("%s\n" % f)
4202 4202
4203 4203 @command('^merge',
4204 4204 [('f', 'force', None, _('force a merge with outstanding changes')),
4205 4205 ('r', 'rev', '', _('revision to merge'), _('REV')),
4206 4206 ('P', 'preview', None,
4207 4207 _('review revisions to merge (no merge is performed)'))
4208 4208 ] + mergetoolopts,
4209 4209 _('[-P] [-f] [[-r] REV]'))
4210 4210 def merge(ui, repo, node=None, **opts):
4211 4211 """merge working directory with another revision
4212 4212
4213 4213 The current working directory is updated with all changes made in
4214 4214 the requested revision since the last common predecessor revision.
4215 4215
4216 4216 Files that changed between either parent are marked as changed for
4217 4217 the next commit and a commit must be performed before any further
4218 4218 updates to the repository are allowed. The next commit will have
4219 4219 two parents.
4220 4220
4221 4221 ``--tool`` can be used to specify the merge tool used for file
4222 4222 merges. It overrides the HGMERGE environment variable and your
4223 4223 configuration files. See :hg:`help merge-tools` for options.
4224 4224
4225 4225 If no revision is specified, the working directory's parent is a
4226 4226 head revision, and the current branch contains exactly one other
4227 4227 head, the other head is merged with by default. Otherwise, an
4228 4228 explicit revision with which to merge with must be provided.
4229 4229
4230 4230 :hg:`resolve` must be used to resolve unresolved files.
4231 4231
4232 4232 To undo an uncommitted merge, use :hg:`update --clean .` which
4233 4233 will check out a clean copy of the original merge parent, losing
4234 4234 all changes.
4235 4235
4236 4236 Returns 0 on success, 1 if there are unresolved files.
4237 4237 """
4238 4238
4239 4239 if opts.get('rev') and node:
4240 4240 raise util.Abort(_("please specify just one revision"))
4241 4241 if not node:
4242 4242 node = opts.get('rev')
4243 4243
4244 4244 if node:
4245 4245 node = scmutil.revsingle(repo, node).node()
4246 4246
4247 4247 if not node and repo._bookmarkcurrent:
4248 4248 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4249 4249 curhead = repo[repo._bookmarkcurrent]
4250 4250 if len(bmheads) == 2:
4251 4251 if curhead == bmheads[0]:
4252 4252 node = bmheads[1]
4253 4253 else:
4254 4254 node = bmheads[0]
4255 4255 elif len(bmheads) > 2:
4256 4256 raise util.Abort(_("multiple matching bookmarks to merge - "
4257 4257 "please merge with an explicit rev or bookmark"),
4258 4258 hint=_("run 'hg heads' to see all heads"))
4259 4259 elif len(bmheads) <= 1:
4260 4260 raise util.Abort(_("no matching bookmark to merge - "
4261 4261 "please merge with an explicit rev or bookmark"),
4262 4262 hint=_("run 'hg heads' to see all heads"))
4263 4263
4264 4264 if not node and not repo._bookmarkcurrent:
4265 4265 branch = repo[None].branch()
4266 4266 bheads = repo.branchheads(branch)
4267 4267 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4268 4268
4269 4269 if len(nbhs) > 2:
4270 4270 raise util.Abort(_("branch '%s' has %d heads - "
4271 4271 "please merge with an explicit rev")
4272 4272 % (branch, len(bheads)),
4273 4273 hint=_("run 'hg heads .' to see heads"))
4274 4274
4275 4275 parent = repo.dirstate.p1()
4276 4276 if len(nbhs) <= 1:
4277 4277 if len(bheads) > 1:
4278 4278 raise util.Abort(_("heads are bookmarked - "
4279 4279 "please merge with an explicit rev"),
4280 4280 hint=_("run 'hg heads' to see all heads"))
4281 4281 if len(repo.heads()) > 1:
4282 4282 raise util.Abort(_("branch '%s' has one head - "
4283 4283 "please merge with an explicit rev")
4284 4284 % branch,
4285 4285 hint=_("run 'hg heads' to see all heads"))
4286 4286 msg, hint = _('nothing to merge'), None
4287 4287 if parent != repo.lookup(branch):
4288 4288 hint = _("use 'hg update' instead")
4289 4289 raise util.Abort(msg, hint=hint)
4290 4290
4291 4291 if parent not in bheads:
4292 4292 raise util.Abort(_('working directory not at a head revision'),
4293 4293 hint=_("use 'hg update' or merge with an "
4294 4294 "explicit revision"))
4295 4295 if parent == nbhs[0]:
4296 4296 node = nbhs[-1]
4297 4297 else:
4298 4298 node = nbhs[0]
4299 4299
4300 4300 if opts.get('preview'):
4301 4301 # find nodes that are ancestors of p2 but not of p1
4302 4302 p1 = repo.lookup('.')
4303 4303 p2 = repo.lookup(node)
4304 4304 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4305 4305
4306 4306 displayer = cmdutil.show_changeset(ui, repo, opts)
4307 4307 for node in nodes:
4308 4308 displayer.show(repo[node])
4309 4309 displayer.close()
4310 4310 return 0
4311 4311
4312 4312 try:
4313 4313 # ui.forcemerge is an internal variable, do not document
4314 4314 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4315 4315 return hg.merge(repo, node, force=opts.get('force'))
4316 4316 finally:
4317 4317 ui.setconfig('ui', 'forcemerge', '')
4318 4318
4319 4319 @command('outgoing|out',
4320 4320 [('f', 'force', None, _('run even when the destination is unrelated')),
4321 4321 ('r', 'rev', [],
4322 4322 _('a changeset intended to be included in the destination'), _('REV')),
4323 4323 ('n', 'newest-first', None, _('show newest record first')),
4324 4324 ('B', 'bookmarks', False, _('compare bookmarks')),
4325 4325 ('b', 'branch', [], _('a specific branch you would like to push'),
4326 4326 _('BRANCH')),
4327 4327 ] + logopts + remoteopts + subrepoopts,
4328 4328 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4329 4329 def outgoing(ui, repo, dest=None, **opts):
4330 4330 """show changesets not found in the destination
4331 4331
4332 4332 Show changesets not found in the specified destination repository
4333 4333 or the default push location. These are the changesets that would
4334 4334 be pushed if a push was requested.
4335 4335
4336 4336 See pull for details of valid destination formats.
4337 4337
4338 4338 Returns 0 if there are outgoing changes, 1 otherwise.
4339 4339 """
4340 4340 if opts.get('graph'):
4341 4341 cmdutil.checkunsupportedgraphflags([], opts)
4342 4342 o = hg._outgoing(ui, repo, dest, opts)
4343 4343 if o is None:
4344 4344 return
4345 4345
4346 4346 revdag = cmdutil.graphrevs(repo, o, opts)
4347 4347 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4348 4348 showparents = [ctx.node() for ctx in repo[None].parents()]
4349 4349 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4350 4350 graphmod.asciiedges)
4351 4351 return 0
4352 4352
4353 4353 if opts.get('bookmarks'):
4354 4354 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4355 4355 dest, branches = hg.parseurl(dest, opts.get('branch'))
4356 4356 other = hg.peer(repo, opts, dest)
4357 4357 if 'bookmarks' not in other.listkeys('namespaces'):
4358 4358 ui.warn(_("remote doesn't support bookmarks\n"))
4359 4359 return 0
4360 4360 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4361 4361 return bookmarks.diff(ui, other, repo)
4362 4362
4363 4363 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4364 4364 try:
4365 4365 return hg.outgoing(ui, repo, dest, opts)
4366 4366 finally:
4367 4367 del repo._subtoppath
4368 4368
4369 4369 @command('parents',
4370 4370 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4371 4371 ] + templateopts,
4372 4372 _('[-r REV] [FILE]'))
4373 4373 def parents(ui, repo, file_=None, **opts):
4374 4374 """show the parents of the working directory or revision
4375 4375
4376 4376 Print the working directory's parent revisions. If a revision is
4377 4377 given via -r/--rev, the parent of that revision will be printed.
4378 4378 If a file argument is given, the revision in which the file was
4379 4379 last changed (before the working directory revision or the
4380 4380 argument to --rev if given) is printed.
4381 4381
4382 4382 Returns 0 on success.
4383 4383 """
4384 4384
4385 4385 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4386 4386
4387 4387 if file_:
4388 4388 m = scmutil.match(ctx, (file_,), opts)
4389 4389 if m.anypats() or len(m.files()) != 1:
4390 4390 raise util.Abort(_('can only specify an explicit filename'))
4391 4391 file_ = m.files()[0]
4392 4392 filenodes = []
4393 4393 for cp in ctx.parents():
4394 4394 if not cp:
4395 4395 continue
4396 4396 try:
4397 4397 filenodes.append(cp.filenode(file_))
4398 4398 except error.LookupError:
4399 4399 pass
4400 4400 if not filenodes:
4401 4401 raise util.Abort(_("'%s' not found in manifest!") % file_)
4402 4402 fl = repo.file(file_)
4403 4403 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4404 4404 else:
4405 4405 p = [cp.node() for cp in ctx.parents()]
4406 4406
4407 4407 displayer = cmdutil.show_changeset(ui, repo, opts)
4408 4408 for n in p:
4409 4409 if n != nullid:
4410 4410 displayer.show(repo[n])
4411 4411 displayer.close()
4412 4412
4413 4413 @command('paths', [], _('[NAME]'))
4414 4414 def paths(ui, repo, search=None):
4415 4415 """show aliases for remote repositories
4416 4416
4417 4417 Show definition of symbolic path name NAME. If no name is given,
4418 4418 show definition of all available names.
4419 4419
4420 4420 Option -q/--quiet suppresses all output when searching for NAME
4421 4421 and shows only the path names when listing all definitions.
4422 4422
4423 4423 Path names are defined in the [paths] section of your
4424 4424 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4425 4425 repository, ``.hg/hgrc`` is used, too.
4426 4426
4427 4427 The path names ``default`` and ``default-push`` have a special
4428 4428 meaning. When performing a push or pull operation, they are used
4429 4429 as fallbacks if no location is specified on the command-line.
4430 4430 When ``default-push`` is set, it will be used for push and
4431 4431 ``default`` will be used for pull; otherwise ``default`` is used
4432 4432 as the fallback for both. When cloning a repository, the clone
4433 4433 source is written as ``default`` in ``.hg/hgrc``. Note that
4434 4434 ``default`` and ``default-push`` apply to all inbound (e.g.
4435 4435 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4436 4436 :hg:`bundle`) operations.
4437 4437
4438 4438 See :hg:`help urls` for more information.
4439 4439
4440 4440 Returns 0 on success.
4441 4441 """
4442 4442 if search:
4443 4443 for name, path in ui.configitems("paths"):
4444 4444 if name == search:
4445 4445 ui.status("%s\n" % util.hidepassword(path))
4446 4446 return
4447 4447 if not ui.quiet:
4448 4448 ui.warn(_("not found!\n"))
4449 4449 return 1
4450 4450 else:
4451 4451 for name, path in ui.configitems("paths"):
4452 4452 if ui.quiet:
4453 4453 ui.write("%s\n" % name)
4454 4454 else:
4455 4455 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4456 4456
4457 4457 @command('^phase',
4458 4458 [('p', 'public', False, _('set changeset phase to public')),
4459 4459 ('d', 'draft', False, _('set changeset phase to draft')),
4460 4460 ('s', 'secret', False, _('set changeset phase to secret')),
4461 4461 ('f', 'force', False, _('allow to move boundary backward')),
4462 4462 ('r', 'rev', [], _('target revision'), _('REV')),
4463 4463 ],
4464 4464 _('[-p|-d|-s] [-f] [-r] REV...'))
4465 4465 def phase(ui, repo, *revs, **opts):
4466 4466 """set or show the current phase name
4467 4467
4468 4468 With no argument, show the phase name of specified revisions.
4469 4469
4470 4470 With one of -p/--public, -d/--draft or -s/--secret, change the
4471 4471 phase value of the specified revisions.
4472 4472
4473 4473 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4474 4474 lower phase to an higher phase. Phases are ordered as follows::
4475 4475
4476 4476 public < draft < secret
4477 4477
4478 4478 Return 0 on success, 1 if no phases were changed or some could not
4479 4479 be changed.
4480 4480 """
4481 4481 # search for a unique phase argument
4482 4482 targetphase = None
4483 4483 for idx, name in enumerate(phases.phasenames):
4484 4484 if opts[name]:
4485 4485 if targetphase is not None:
4486 4486 raise util.Abort(_('only one phase can be specified'))
4487 4487 targetphase = idx
4488 4488
4489 4489 # look for specified revision
4490 4490 revs = list(revs)
4491 4491 revs.extend(opts['rev'])
4492 4492 if not revs:
4493 4493 raise util.Abort(_('no revisions specified'))
4494 4494
4495 4495 revs = scmutil.revrange(repo, revs)
4496 4496
4497 4497 lock = None
4498 4498 ret = 0
4499 4499 if targetphase is None:
4500 4500 # display
4501 4501 for r in revs:
4502 4502 ctx = repo[r]
4503 4503 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4504 4504 else:
4505 4505 lock = repo.lock()
4506 4506 try:
4507 4507 # set phase
4508 4508 if not revs:
4509 4509 raise util.Abort(_('empty revision set'))
4510 4510 nodes = [repo[r].node() for r in revs]
4511 4511 olddata = repo._phasecache.getphaserevs(repo)[:]
4512 4512 phases.advanceboundary(repo, targetphase, nodes)
4513 4513 if opts['force']:
4514 4514 phases.retractboundary(repo, targetphase, nodes)
4515 4515 finally:
4516 4516 lock.release()
4517 4517 newdata = repo._phasecache.getphaserevs(repo)
4518 4518 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4519 4519 rejected = [n for n in nodes
4520 4520 if newdata[repo[n].rev()] < targetphase]
4521 4521 if rejected:
4522 4522 ui.warn(_('cannot move %i changesets to a more permissive '
4523 4523 'phase, use --force\n') % len(rejected))
4524 4524 ret = 1
4525 4525 if changes:
4526 4526 msg = _('phase changed for %i changesets\n') % changes
4527 4527 if ret:
4528 4528 ui.status(msg)
4529 4529 else:
4530 4530 ui.note(msg)
4531 4531 else:
4532 4532 ui.warn(_('no phases changed\n'))
4533 4533 ret = 1
4534 4534 return ret
4535 4535
4536 4536 def postincoming(ui, repo, modheads, optupdate, checkout):
4537 4537 if modheads == 0:
4538 4538 return
4539 4539 if optupdate:
4540 4540 movemarkfrom = repo['.'].node()
4541 4541 try:
4542 4542 ret = hg.update(repo, checkout)
4543 4543 except util.Abort, inst:
4544 4544 ui.warn(_("not updating: %s\n") % str(inst))
4545 4545 return 0
4546 4546 if not ret and not checkout:
4547 4547 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4548 4548 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4549 4549 return ret
4550 4550 if modheads > 1:
4551 4551 currentbranchheads = len(repo.branchheads())
4552 4552 if currentbranchheads == modheads:
4553 4553 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4554 4554 elif currentbranchheads > 1:
4555 4555 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4556 4556 "merge)\n"))
4557 4557 else:
4558 4558 ui.status(_("(run 'hg heads' to see heads)\n"))
4559 4559 else:
4560 4560 ui.status(_("(run 'hg update' to get a working copy)\n"))
4561 4561
4562 4562 @command('^pull',
4563 4563 [('u', 'update', None,
4564 4564 _('update to new branch head if changesets were pulled')),
4565 4565 ('f', 'force', None, _('run even when remote repository is unrelated')),
4566 4566 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4567 4567 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4568 4568 ('b', 'branch', [], _('a specific branch you would like to pull'),
4569 4569 _('BRANCH')),
4570 4570 ] + remoteopts,
4571 4571 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4572 4572 def pull(ui, repo, source="default", **opts):
4573 4573 """pull changes from the specified source
4574 4574
4575 4575 Pull changes from a remote repository to a local one.
4576 4576
4577 4577 This finds all changes from the repository at the specified path
4578 4578 or URL and adds them to a local repository (the current one unless
4579 4579 -R is specified). By default, this does not update the copy of the
4580 4580 project in the working directory.
4581 4581
4582 4582 Use :hg:`incoming` if you want to see what would have been added
4583 4583 by a pull at the time you issued this command. If you then decide
4584 4584 to add those changes to the repository, you should use :hg:`pull
4585 4585 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4586 4586
4587 4587 If SOURCE is omitted, the 'default' path will be used.
4588 4588 See :hg:`help urls` for more information.
4589 4589
4590 4590 Returns 0 on success, 1 if an update had unresolved files.
4591 4591 """
4592 4592 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4593 4593 other = hg.peer(repo, opts, source)
4594 4594 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4595 4595 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4596 4596
4597 4597 if opts.get('bookmark'):
4598 4598 if not revs:
4599 4599 revs = []
4600 4600 rb = other.listkeys('bookmarks')
4601 4601 for b in opts['bookmark']:
4602 4602 if b not in rb:
4603 4603 raise util.Abort(_('remote bookmark %s not found!') % b)
4604 4604 revs.append(rb[b])
4605 4605
4606 4606 if revs:
4607 4607 try:
4608 4608 revs = [other.lookup(rev) for rev in revs]
4609 4609 except error.CapabilityError:
4610 4610 err = _("other repository doesn't support revision lookup, "
4611 4611 "so a rev cannot be specified.")
4612 4612 raise util.Abort(err)
4613 4613
4614 4614 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4615 4615 bookmarks.updatefromremote(ui, repo, other, source)
4616 4616 if checkout:
4617 4617 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4618 4618 repo._subtoppath = source
4619 4619 try:
4620 4620 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4621 4621
4622 4622 finally:
4623 4623 del repo._subtoppath
4624 4624
4625 4625 # update specified bookmarks
4626 4626 if opts.get('bookmark'):
4627 4627 for b in opts['bookmark']:
4628 4628 # explicit pull overrides local bookmark if any
4629 4629 ui.status(_("importing bookmark %s\n") % b)
4630 4630 repo._bookmarks[b] = repo[rb[b]].node()
4631 4631 bookmarks.write(repo)
4632 4632
4633 4633 return ret
4634 4634
4635 4635 @command('^push',
4636 4636 [('f', 'force', None, _('force push')),
4637 4637 ('r', 'rev', [],
4638 4638 _('a changeset intended to be included in the destination'),
4639 4639 _('REV')),
4640 4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4641 4641 ('b', 'branch', [],
4642 4642 _('a specific branch you would like to push'), _('BRANCH')),
4643 4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4644 4644 ] + remoteopts,
4645 4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4646 4646 def push(ui, repo, dest=None, **opts):
4647 4647 """push changes to the specified destination
4648 4648
4649 4649 Push changesets from the local repository to the specified
4650 4650 destination.
4651 4651
4652 4652 This operation is symmetrical to pull: it is identical to a pull
4653 4653 in the destination repository from the current one.
4654 4654
4655 4655 By default, push will not allow creation of new heads at the
4656 4656 destination, since multiple heads would make it unclear which head
4657 4657 to use. In this situation, it is recommended to pull and merge
4658 4658 before pushing.
4659 4659
4660 4660 Use --new-branch if you want to allow push to create a new named
4661 4661 branch that is not present at the destination. This allows you to
4662 4662 only create a new branch without forcing other changes.
4663 4663
4664 4664 Use -f/--force to override the default behavior and push all
4665 4665 changesets on all branches.
4666 4666
4667 4667 If -r/--rev is used, the specified revision and all its ancestors
4668 4668 will be pushed to the remote repository.
4669 4669
4670 4670 If -B/--bookmark is used, the specified bookmarked revision, its
4671 4671 ancestors, and the bookmark will be pushed to the remote
4672 4672 repository.
4673 4673
4674 4674 Please see :hg:`help urls` for important details about ``ssh://``
4675 4675 URLs. If DESTINATION is omitted, a default path will be used.
4676 4676
4677 4677 Returns 0 if push was successful, 1 if nothing to push.
4678 4678 """
4679 4679
4680 4680 if opts.get('bookmark'):
4681 4681 for b in opts['bookmark']:
4682 4682 # translate -B options to -r so changesets get pushed
4683 4683 if b in repo._bookmarks:
4684 4684 opts.setdefault('rev', []).append(b)
4685 4685 else:
4686 4686 # if we try to push a deleted bookmark, translate it to null
4687 4687 # this lets simultaneous -r, -b options continue working
4688 4688 opts.setdefault('rev', []).append("null")
4689 4689
4690 4690 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4691 4691 dest, branches = hg.parseurl(dest, opts.get('branch'))
4692 4692 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4693 4693 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4694 4694 other = hg.peer(repo, opts, dest)
4695 4695 if revs:
4696 4696 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4697 4697
4698 4698 repo._subtoppath = dest
4699 4699 try:
4700 4700 # push subrepos depth-first for coherent ordering
4701 4701 c = repo['']
4702 4702 subs = c.substate # only repos that are committed
4703 4703 for s in sorted(subs):
4704 4704 if c.sub(s).push(opts) == 0:
4705 4705 return False
4706 4706 finally:
4707 4707 del repo._subtoppath
4708 4708 result = repo.push(other, opts.get('force'), revs=revs,
4709 4709 newbranch=opts.get('new_branch'))
4710 4710
4711 4711 result = not result
4712 4712
4713 4713 if opts.get('bookmark'):
4714 4714 rb = other.listkeys('bookmarks')
4715 4715 for b in opts['bookmark']:
4716 4716 # explicit push overrides remote bookmark if any
4717 4717 if b in repo._bookmarks:
4718 4718 ui.status(_("exporting bookmark %s\n") % b)
4719 4719 new = repo[b].hex()
4720 4720 elif b in rb:
4721 4721 ui.status(_("deleting remote bookmark %s\n") % b)
4722 4722 new = '' # delete
4723 4723 else:
4724 4724 ui.warn(_('bookmark %s does not exist on the local '
4725 4725 'or remote repository!\n') % b)
4726 4726 return 2
4727 4727 old = rb.get(b, '')
4728 4728 r = other.pushkey('bookmarks', b, old, new)
4729 4729 if not r:
4730 4730 ui.warn(_('updating bookmark %s failed!\n') % b)
4731 4731 if not result:
4732 4732 result = 2
4733 4733
4734 4734 return result
4735 4735
4736 4736 @command('recover', [])
4737 4737 def recover(ui, repo):
4738 4738 """roll back an interrupted transaction
4739 4739
4740 4740 Recover from an interrupted commit or pull.
4741 4741
4742 4742 This command tries to fix the repository status after an
4743 4743 interrupted operation. It should only be necessary when Mercurial
4744 4744 suggests it.
4745 4745
4746 4746 Returns 0 if successful, 1 if nothing to recover or verify fails.
4747 4747 """
4748 4748 if repo.recover():
4749 4749 return hg.verify(repo)
4750 4750 return 1
4751 4751
4752 4752 @command('^remove|rm',
4753 4753 [('A', 'after', None, _('record delete for missing files')),
4754 4754 ('f', 'force', None,
4755 4755 _('remove (and delete) file even if added or modified')),
4756 4756 ] + walkopts,
4757 4757 _('[OPTION]... FILE...'))
4758 4758 def remove(ui, repo, *pats, **opts):
4759 4759 """remove the specified files on the next commit
4760 4760
4761 4761 Schedule the indicated files for removal from the current branch.
4762 4762
4763 4763 This command schedules the files to be removed at the next commit.
4764 4764 To undo a remove before that, see :hg:`revert`. To undo added
4765 4765 files, see :hg:`forget`.
4766 4766
4767 4767 .. container:: verbose
4768 4768
4769 4769 -A/--after can be used to remove only files that have already
4770 4770 been deleted, -f/--force can be used to force deletion, and -Af
4771 4771 can be used to remove files from the next revision without
4772 4772 deleting them from the working directory.
4773 4773
4774 4774 The following table details the behavior of remove for different
4775 4775 file states (columns) and option combinations (rows). The file
4776 4776 states are Added [A], Clean [C], Modified [M] and Missing [!]
4777 4777 (as reported by :hg:`status`). The actions are Warn, Remove
4778 4778 (from branch) and Delete (from disk):
4779 4779
4780 4780 ======= == == == ==
4781 4781 A C M !
4782 4782 ======= == == == ==
4783 4783 none W RD W R
4784 4784 -f R RD RD R
4785 4785 -A W W W R
4786 4786 -Af R R R R
4787 4787 ======= == == == ==
4788 4788
4789 4789 Note that remove never deletes files in Added [A] state from the
4790 4790 working directory, not even if option --force is specified.
4791 4791
4792 4792 Returns 0 on success, 1 if any warnings encountered.
4793 4793 """
4794 4794
4795 4795 ret = 0
4796 4796 after, force = opts.get('after'), opts.get('force')
4797 4797 if not pats and not after:
4798 4798 raise util.Abort(_('no files specified'))
4799 4799
4800 4800 m = scmutil.match(repo[None], pats, opts)
4801 4801 s = repo.status(match=m, clean=True)
4802 4802 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4803 4803
4804 4804 for f in m.files():
4805 4805 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4806 4806 if os.path.exists(m.rel(f)):
4807 4807 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4808 4808 ret = 1
4809 4809
4810 4810 if force:
4811 4811 list = modified + deleted + clean + added
4812 4812 elif after:
4813 4813 list = deleted
4814 4814 for f in modified + added + clean:
4815 4815 ui.warn(_('not removing %s: file still exists (use -f'
4816 4816 ' to force removal)\n') % m.rel(f))
4817 4817 ret = 1
4818 4818 else:
4819 4819 list = deleted + clean
4820 4820 for f in modified:
4821 4821 ui.warn(_('not removing %s: file is modified (use -f'
4822 4822 ' to force removal)\n') % m.rel(f))
4823 4823 ret = 1
4824 4824 for f in added:
4825 4825 ui.warn(_('not removing %s: file has been marked for add'
4826 4826 ' (use forget to undo)\n') % m.rel(f))
4827 4827 ret = 1
4828 4828
4829 4829 for f in sorted(list):
4830 4830 if ui.verbose or not m.exact(f):
4831 4831 ui.status(_('removing %s\n') % m.rel(f))
4832 4832
4833 4833 wlock = repo.wlock()
4834 4834 try:
4835 4835 if not after:
4836 4836 for f in list:
4837 4837 if f in added:
4838 4838 continue # we never unlink added files on remove
4839 4839 try:
4840 4840 util.unlinkpath(repo.wjoin(f))
4841 4841 except OSError, inst:
4842 4842 if inst.errno != errno.ENOENT:
4843 4843 raise
4844 4844 repo[None].forget(list)
4845 4845 finally:
4846 4846 wlock.release()
4847 4847
4848 4848 return ret
4849 4849
4850 4850 @command('rename|move|mv',
4851 4851 [('A', 'after', None, _('record a rename that has already occurred')),
4852 4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4853 4853 ] + walkopts + dryrunopts,
4854 4854 _('[OPTION]... SOURCE... DEST'))
4855 4855 def rename(ui, repo, *pats, **opts):
4856 4856 """rename files; equivalent of copy + remove
4857 4857
4858 4858 Mark dest as copies of sources; mark sources for deletion. If dest
4859 4859 is a directory, copies are put in that directory. If dest is a
4860 4860 file, there can only be one source.
4861 4861
4862 4862 By default, this command copies the contents of files as they
4863 4863 exist in the working directory. If invoked with -A/--after, the
4864 4864 operation is recorded, but no copying is performed.
4865 4865
4866 4866 This command takes effect at the next commit. To undo a rename
4867 4867 before that, see :hg:`revert`.
4868 4868
4869 4869 Returns 0 on success, 1 if errors are encountered.
4870 4870 """
4871 4871 wlock = repo.wlock(False)
4872 4872 try:
4873 4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4874 4874 finally:
4875 4875 wlock.release()
4876 4876
4877 4877 @command('resolve',
4878 4878 [('a', 'all', None, _('select all unresolved files')),
4879 4879 ('l', 'list', None, _('list state of files needing merge')),
4880 4880 ('m', 'mark', None, _('mark files as resolved')),
4881 4881 ('u', 'unmark', None, _('mark files as unresolved')),
4882 4882 ('n', 'no-status', None, _('hide status prefix'))]
4883 4883 + mergetoolopts + walkopts,
4884 4884 _('[OPTION]... [FILE]...'))
4885 4885 def resolve(ui, repo, *pats, **opts):
4886 4886 """redo merges or set/view the merge status of files
4887 4887
4888 4888 Merges with unresolved conflicts are often the result of
4889 4889 non-interactive merging using the ``internal:merge`` configuration
4890 4890 setting, or a command-line merge tool like ``diff3``. The resolve
4891 4891 command is used to manage the files involved in a merge, after
4892 4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4893 4893 working directory must have two parents). See :hg:`help
4894 4894 merge-tools` for information on configuring merge tools.
4895 4895
4896 4896 The resolve command can be used in the following ways:
4897 4897
4898 4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4899 4899 files, discarding any previous merge attempts. Re-merging is not
4900 4900 performed for files already marked as resolved. Use ``--all/-a``
4901 4901 to select all unresolved files. ``--tool`` can be used to specify
4902 4902 the merge tool used for the given files. It overrides the HGMERGE
4903 4903 environment variable and your configuration files. Previous file
4904 4904 contents are saved with a ``.orig`` suffix.
4905 4905
4906 4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4907 4907 (e.g. after having manually fixed-up the files). The default is
4908 4908 to mark all unresolved files.
4909 4909
4910 4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4911 4911 default is to mark all resolved files.
4912 4912
4913 4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4914 4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4915 4915
4916 4916 Note that Mercurial will not let you commit files with unresolved
4917 4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4918 4918 commit after a conflicting merge.
4919 4919
4920 4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4921 4921 """
4922 4922
4923 4923 all, mark, unmark, show, nostatus = \
4924 4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4925 4925
4926 4926 if (show and (mark or unmark)) or (mark and unmark):
4927 4927 raise util.Abort(_("too many options specified"))
4928 4928 if pats and all:
4929 4929 raise util.Abort(_("can't specify --all and patterns"))
4930 4930 if not (all or pats or show or mark or unmark):
4931 4931 raise util.Abort(_('no files or directories specified; '
4932 4932 'use --all to remerge all files'))
4933 4933
4934 4934 ms = mergemod.mergestate(repo)
4935 4935 m = scmutil.match(repo[None], pats, opts)
4936 4936 ret = 0
4937 4937
4938 4938 for f in ms:
4939 4939 if m(f):
4940 4940 if show:
4941 4941 if nostatus:
4942 4942 ui.write("%s\n" % f)
4943 4943 else:
4944 4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4945 4945 label='resolve.' +
4946 4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4947 4947 elif mark:
4948 4948 ms.mark(f, "r")
4949 4949 elif unmark:
4950 4950 ms.mark(f, "u")
4951 4951 else:
4952 4952 wctx = repo[None]
4953 4953 mctx = wctx.parents()[-1]
4954 4954
4955 4955 # backup pre-resolve (merge uses .orig for its own purposes)
4956 4956 a = repo.wjoin(f)
4957 4957 util.copyfile(a, a + ".resolve")
4958 4958
4959 4959 try:
4960 4960 # resolve file
4961 4961 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4962 4962 if ms.resolve(f, wctx, mctx):
4963 4963 ret = 1
4964 4964 finally:
4965 4965 ui.setconfig('ui', 'forcemerge', '')
4966 4966
4967 4967 # replace filemerge's .orig file with our resolve file
4968 4968 util.rename(a + ".resolve", a + ".orig")
4969 4969
4970 4970 ms.commit()
4971 4971 return ret
4972 4972
4973 4973 @command('revert',
4974 4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4975 4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4976 4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4977 4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4978 4978 ] + walkopts + dryrunopts,
4979 4979 _('[OPTION]... [-r REV] [NAME]...'))
4980 4980 def revert(ui, repo, *pats, **opts):
4981 4981 """restore files to their checkout state
4982 4982
4983 4983 .. note::
4984 4984
4985 4985 To check out earlier revisions, you should use :hg:`update REV`.
4986 4986 To cancel an uncommitted merge (and lose your changes), use
4987 4987 :hg:`update --clean .`.
4988 4988
4989 4989 With no revision specified, revert the specified files or directories
4990 4990 to the contents they had in the parent of the working directory.
4991 4991 This restores the contents of files to an unmodified
4992 4992 state and unschedules adds, removes, copies, and renames. If the
4993 4993 working directory has two parents, you must explicitly specify a
4994 4994 revision.
4995 4995
4996 4996 Using the -r/--rev or -d/--date options, revert the given files or
4997 4997 directories to their states as of a specific revision. Because
4998 4998 revert does not change the working directory parents, this will
4999 4999 cause these files to appear modified. This can be helpful to "back
5000 5000 out" some or all of an earlier change. See :hg:`backout` for a
5001 5001 related method.
5002 5002
5003 5003 Modified files are saved with a .orig suffix before reverting.
5004 5004 To disable these backups, use --no-backup.
5005 5005
5006 5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5007 5007
5008 5008 Returns 0 on success.
5009 5009 """
5010 5010
5011 5011 if opts.get("date"):
5012 5012 if opts.get("rev"):
5013 5013 raise util.Abort(_("you can't specify a revision and a date"))
5014 5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5015 5015
5016 5016 parent, p2 = repo.dirstate.parents()
5017 5017 if not opts.get('rev') and p2 != nullid:
5018 5018 # revert after merge is a trap for new users (issue2915)
5019 5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5020 5020 hint=_('use "hg update" or see "hg help revert"'))
5021 5021
5022 5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5023 5023
5024 5024 if not pats and not opts.get('all'):
5025 5025 msg = _("no files or directories specified")
5026 5026 if p2 != nullid:
5027 5027 hint = _("uncommitted merge, use --all to discard all changes,"
5028 5028 " or 'hg update -C .' to abort the merge")
5029 5029 raise util.Abort(msg, hint=hint)
5030 5030 dirty = util.any(repo.status())
5031 5031 node = ctx.node()
5032 5032 if node != parent:
5033 5033 if dirty:
5034 5034 hint = _("uncommitted changes, use --all to discard all"
5035 5035 " changes, or 'hg update %s' to update") % ctx.rev()
5036 5036 else:
5037 5037 hint = _("use --all to revert all files,"
5038 5038 " or 'hg update %s' to update") % ctx.rev()
5039 5039 elif dirty:
5040 5040 hint = _("uncommitted changes, use --all to discard all changes")
5041 5041 else:
5042 5042 hint = _("use --all to revert all files")
5043 5043 raise util.Abort(msg, hint=hint)
5044 5044
5045 5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5046 5046
5047 5047 @command('rollback', dryrunopts +
5048 5048 [('f', 'force', False, _('ignore safety measures'))])
5049 5049 def rollback(ui, repo, **opts):
5050 5050 """roll back the last transaction (dangerous)
5051 5051
5052 5052 This command should be used with care. There is only one level of
5053 5053 rollback, and there is no way to undo a rollback. It will also
5054 5054 restore the dirstate at the time of the last transaction, losing
5055 5055 any dirstate changes since that time. This command does not alter
5056 5056 the working directory.
5057 5057
5058 5058 Transactions are used to encapsulate the effects of all commands
5059 5059 that create new changesets or propagate existing changesets into a
5060 5060 repository.
5061 5061
5062 5062 .. container:: verbose
5063 5063
5064 5064 For example, the following commands are transactional, and their
5065 5065 effects can be rolled back:
5066 5066
5067 5067 - commit
5068 5068 - import
5069 5069 - pull
5070 5070 - push (with this repository as the destination)
5071 5071 - unbundle
5072 5072
5073 5073 To avoid permanent data loss, rollback will refuse to rollback a
5074 5074 commit transaction if it isn't checked out. Use --force to
5075 5075 override this protection.
5076 5076
5077 5077 This command is not intended for use on public repositories. Once
5078 5078 changes are visible for pull by other users, rolling a transaction
5079 5079 back locally is ineffective (someone else may already have pulled
5080 5080 the changes). Furthermore, a race is possible with readers of the
5081 5081 repository; for example an in-progress pull from the repository
5082 5082 may fail if a rollback is performed.
5083 5083
5084 5084 Returns 0 on success, 1 if no rollback data is available.
5085 5085 """
5086 5086 return repo.rollback(dryrun=opts.get('dry_run'),
5087 5087 force=opts.get('force'))
5088 5088
5089 5089 @command('root', [])
5090 5090 def root(ui, repo):
5091 5091 """print the root (top) of the current working directory
5092 5092
5093 5093 Print the root directory of the current repository.
5094 5094
5095 5095 Returns 0 on success.
5096 5096 """
5097 5097 ui.write(repo.root + "\n")
5098 5098
5099 5099 @command('^serve',
5100 5100 [('A', 'accesslog', '', _('name of access log file to write to'),
5101 5101 _('FILE')),
5102 5102 ('d', 'daemon', None, _('run server in background')),
5103 5103 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5104 5104 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5105 5105 # use string type, then we can check if something was passed
5106 5106 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5107 5107 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5108 5108 _('ADDR')),
5109 5109 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5110 5110 _('PREFIX')),
5111 5111 ('n', 'name', '',
5112 5112 _('name to show in web pages (default: working directory)'), _('NAME')),
5113 5113 ('', 'web-conf', '',
5114 5114 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5115 5115 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5116 5116 _('FILE')),
5117 5117 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5118 5118 ('', 'stdio', None, _('for remote clients')),
5119 5119 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5120 5120 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5121 5121 ('', 'style', '', _('template style to use'), _('STYLE')),
5122 5122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5123 5123 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5124 5124 _('[OPTION]...'))
5125 5125 def serve(ui, repo, **opts):
5126 5126 """start stand-alone webserver
5127 5127
5128 5128 Start a local HTTP repository browser and pull server. You can use
5129 5129 this for ad-hoc sharing and browsing of repositories. It is
5130 5130 recommended to use a real web server to serve a repository for
5131 5131 longer periods of time.
5132 5132
5133 5133 Please note that the server does not implement access control.
5134 5134 This means that, by default, anybody can read from the server and
5135 5135 nobody can write to it by default. Set the ``web.allow_push``
5136 5136 option to ``*`` to allow everybody to push to the server. You
5137 5137 should use a real web server if you need to authenticate users.
5138 5138
5139 5139 By default, the server logs accesses to stdout and errors to
5140 5140 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5141 5141 files.
5142 5142
5143 5143 To have the server choose a free port number to listen on, specify
5144 5144 a port number of 0; in this case, the server will print the port
5145 5145 number it uses.
5146 5146
5147 5147 Returns 0 on success.
5148 5148 """
5149 5149
5150 5150 if opts["stdio"] and opts["cmdserver"]:
5151 5151 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5152 5152
5153 5153 def checkrepo():
5154 5154 if repo is None:
5155 5155 raise error.RepoError(_("there is no Mercurial repository here"
5156 5156 " (.hg not found)"))
5157 5157
5158 5158 if opts["stdio"]:
5159 5159 checkrepo()
5160 5160 s = sshserver.sshserver(ui, repo)
5161 5161 s.serve_forever()
5162 5162
5163 5163 if opts["cmdserver"]:
5164 5164 checkrepo()
5165 5165 s = commandserver.server(ui, repo, opts["cmdserver"])
5166 5166 return s.serve()
5167 5167
5168 5168 # this way we can check if something was given in the command-line
5169 5169 if opts.get('port'):
5170 5170 opts['port'] = util.getport(opts.get('port'))
5171 5171
5172 5172 baseui = repo and repo.baseui or ui
5173 5173 optlist = ("name templates style address port prefix ipv6"
5174 5174 " accesslog errorlog certificate encoding")
5175 5175 for o in optlist.split():
5176 5176 val = opts.get(o, '')
5177 5177 if val in (None, ''): # should check against default options instead
5178 5178 continue
5179 5179 baseui.setconfig("web", o, val)
5180 5180 if repo and repo.ui != baseui:
5181 5181 repo.ui.setconfig("web", o, val)
5182 5182
5183 5183 o = opts.get('web_conf') or opts.get('webdir_conf')
5184 5184 if not o:
5185 5185 if not repo:
5186 5186 raise error.RepoError(_("there is no Mercurial repository"
5187 5187 " here (.hg not found)"))
5188 5188 o = repo.root
5189 5189
5190 5190 app = hgweb.hgweb(o, baseui=ui)
5191 5191
5192 5192 class service(object):
5193 5193 def init(self):
5194 5194 util.setsignalhandler()
5195 5195 self.httpd = hgweb.server.create_server(ui, app)
5196 5196
5197 5197 if opts['port'] and not ui.verbose:
5198 5198 return
5199 5199
5200 5200 if self.httpd.prefix:
5201 5201 prefix = self.httpd.prefix.strip('/') + '/'
5202 5202 else:
5203 5203 prefix = ''
5204 5204
5205 5205 port = ':%d' % self.httpd.port
5206 5206 if port == ':80':
5207 5207 port = ''
5208 5208
5209 5209 bindaddr = self.httpd.addr
5210 5210 if bindaddr == '0.0.0.0':
5211 5211 bindaddr = '*'
5212 5212 elif ':' in bindaddr: # IPv6
5213 5213 bindaddr = '[%s]' % bindaddr
5214 5214
5215 5215 fqaddr = self.httpd.fqaddr
5216 5216 if ':' in fqaddr:
5217 5217 fqaddr = '[%s]' % fqaddr
5218 5218 if opts['port']:
5219 5219 write = ui.status
5220 5220 else:
5221 5221 write = ui.write
5222 5222 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5223 5223 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5224 5224
5225 5225 def run(self):
5226 5226 self.httpd.serve_forever()
5227 5227
5228 5228 service = service()
5229 5229
5230 5230 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5231 5231
5232 5232 @command('showconfig|debugconfig',
5233 5233 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5234 5234 _('[-u] [NAME]...'))
5235 5235 def showconfig(ui, repo, *values, **opts):
5236 5236 """show combined config settings from all hgrc files
5237 5237
5238 5238 With no arguments, print names and values of all config items.
5239 5239
5240 5240 With one argument of the form section.name, print just the value
5241 5241 of that config item.
5242 5242
5243 5243 With multiple arguments, print names and values of all config
5244 5244 items with matching section names.
5245 5245
5246 5246 With --debug, the source (filename and line number) is printed
5247 5247 for each config item.
5248 5248
5249 5249 Returns 0 on success.
5250 5250 """
5251 5251
5252 5252 for f in scmutil.rcpath():
5253 5253 ui.debug('read config from: %s\n' % f)
5254 5254 untrusted = bool(opts.get('untrusted'))
5255 5255 if values:
5256 5256 sections = [v for v in values if '.' not in v]
5257 5257 items = [v for v in values if '.' in v]
5258 5258 if len(items) > 1 or items and sections:
5259 5259 raise util.Abort(_('only one config item permitted'))
5260 5260 for section, name, value in ui.walkconfig(untrusted=untrusted):
5261 5261 value = str(value).replace('\n', '\\n')
5262 5262 sectname = section + '.' + name
5263 5263 if values:
5264 5264 for v in values:
5265 5265 if v == section:
5266 5266 ui.debug('%s: ' %
5267 5267 ui.configsource(section, name, untrusted))
5268 5268 ui.write('%s=%s\n' % (sectname, value))
5269 5269 elif v == sectname:
5270 5270 ui.debug('%s: ' %
5271 5271 ui.configsource(section, name, untrusted))
5272 5272 ui.write(value, '\n')
5273 5273 else:
5274 5274 ui.debug('%s: ' %
5275 5275 ui.configsource(section, name, untrusted))
5276 5276 ui.write('%s=%s\n' % (sectname, value))
5277 5277
5278 5278 @command('^status|st',
5279 5279 [('A', 'all', None, _('show status of all files')),
5280 5280 ('m', 'modified', None, _('show only modified files')),
5281 5281 ('a', 'added', None, _('show only added files')),
5282 5282 ('r', 'removed', None, _('show only removed files')),
5283 5283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5284 5284 ('c', 'clean', None, _('show only files without changes')),
5285 5285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5286 5286 ('i', 'ignored', None, _('show only ignored files')),
5287 5287 ('n', 'no-status', None, _('hide status prefix')),
5288 5288 ('C', 'copies', None, _('show source of copied files')),
5289 5289 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5290 5290 ('', 'rev', [], _('show difference from revision'), _('REV')),
5291 5291 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5292 5292 ] + walkopts + subrepoopts,
5293 5293 _('[OPTION]... [FILE]...'))
5294 5294 def status(ui, repo, *pats, **opts):
5295 5295 """show changed files in the working directory
5296 5296
5297 5297 Show status of files in the repository. If names are given, only
5298 5298 files that match are shown. Files that are clean or ignored or
5299 5299 the source of a copy/move operation, are not listed unless
5300 5300 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5301 5301 Unless options described with "show only ..." are given, the
5302 5302 options -mardu are used.
5303 5303
5304 5304 Option -q/--quiet hides untracked (unknown and ignored) files
5305 5305 unless explicitly requested with -u/--unknown or -i/--ignored.
5306 5306
5307 5307 .. note::
5308 5308 status may appear to disagree with diff if permissions have
5309 5309 changed or a merge has occurred. The standard diff format does
5310 5310 not report permission changes and diff only reports changes
5311 5311 relative to one merge parent.
5312 5312
5313 5313 If one revision is given, it is used as the base revision.
5314 5314 If two revisions are given, the differences between them are
5315 5315 shown. The --change option can also be used as a shortcut to list
5316 5316 the changed files of a revision from its first parent.
5317 5317
5318 5318 The codes used to show the status of files are::
5319 5319
5320 5320 M = modified
5321 5321 A = added
5322 5322 R = removed
5323 5323 C = clean
5324 5324 ! = missing (deleted by non-hg command, but still tracked)
5325 5325 ? = not tracked
5326 5326 I = ignored
5327 5327 = origin of the previous file listed as A (added)
5328 5328
5329 5329 .. container:: verbose
5330 5330
5331 5331 Examples:
5332 5332
5333 5333 - show changes in the working directory relative to a
5334 5334 changeset::
5335 5335
5336 5336 hg status --rev 9353
5337 5337
5338 5338 - show all changes including copies in an existing changeset::
5339 5339
5340 5340 hg status --copies --change 9353
5341 5341
5342 5342 - get a NUL separated list of added files, suitable for xargs::
5343 5343
5344 5344 hg status -an0
5345 5345
5346 5346 Returns 0 on success.
5347 5347 """
5348 5348
5349 5349 revs = opts.get('rev')
5350 5350 change = opts.get('change')
5351 5351
5352 5352 if revs and change:
5353 5353 msg = _('cannot specify --rev and --change at the same time')
5354 5354 raise util.Abort(msg)
5355 5355 elif change:
5356 5356 node2 = scmutil.revsingle(repo, change, None).node()
5357 5357 node1 = repo[node2].p1().node()
5358 5358 else:
5359 5359 node1, node2 = scmutil.revpair(repo, revs)
5360 5360
5361 5361 cwd = (pats and repo.getcwd()) or ''
5362 5362 end = opts.get('print0') and '\0' or '\n'
5363 5363 copy = {}
5364 5364 states = 'modified added removed deleted unknown ignored clean'.split()
5365 5365 show = [k for k in states if opts.get(k)]
5366 5366 if opts.get('all'):
5367 5367 show += ui.quiet and (states[:4] + ['clean']) or states
5368 5368 if not show:
5369 5369 show = ui.quiet and states[:4] or states[:5]
5370 5370
5371 5371 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5372 5372 'ignored' in show, 'clean' in show, 'unknown' in show,
5373 5373 opts.get('subrepos'))
5374 5374 changestates = zip(states, 'MAR!?IC', stat)
5375 5375
5376 5376 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5377 5377 copy = copies.pathcopies(repo[node1], repo[node2])
5378 5378
5379 5379 fm = ui.formatter('status', opts)
5380 5380 format = '%s %s' + end
5381 5381 if opts.get('no_status'):
5382 5382 format = '%.0s%s' + end
5383 5383
5384 5384 for state, char, files in changestates:
5385 5385 if state in show:
5386 5386 label = 'status.' + state
5387 5387 for f in files:
5388 5388 fm.startitem()
5389 5389 fm.write("status path", format, char,
5390 5390 repo.pathto(f, cwd), label=label)
5391 5391 if f in copy:
5392 5392 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5393 5393 label='status.copied')
5394 5394 fm.end()
5395 5395
5396 5396 @command('^summary|sum',
5397 5397 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5398 5398 def summary(ui, repo, **opts):
5399 5399 """summarize working directory state
5400 5400
5401 5401 This generates a brief summary of the working directory state,
5402 5402 including parents, branch, commit status, and available updates.
5403 5403
5404 5404 With the --remote option, this will check the default paths for
5405 5405 incoming and outgoing changes. This can be time-consuming.
5406 5406
5407 5407 Returns 0 on success.
5408 5408 """
5409 5409
5410 5410 ctx = repo[None]
5411 5411 parents = ctx.parents()
5412 5412 pnode = parents[0].node()
5413 5413 marks = []
5414 5414
5415 5415 for p in parents:
5416 5416 # label with log.changeset (instead of log.parent) since this
5417 5417 # shows a working directory parent *changeset*:
5418 5418 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5419 5419 label='log.changeset')
5420 5420 ui.write(' '.join(p.tags()), label='log.tag')
5421 5421 if p.bookmarks():
5422 5422 marks.extend(p.bookmarks())
5423 5423 if p.rev() == -1:
5424 5424 if not len(repo):
5425 5425 ui.write(_(' (empty repository)'))
5426 5426 else:
5427 5427 ui.write(_(' (no revision checked out)'))
5428 5428 ui.write('\n')
5429 5429 if p.description():
5430 5430 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5431 5431 label='log.summary')
5432 5432
5433 5433 branch = ctx.branch()
5434 5434 bheads = repo.branchheads(branch)
5435 5435 m = _('branch: %s\n') % branch
5436 5436 if branch != 'default':
5437 5437 ui.write(m, label='log.branch')
5438 5438 else:
5439 5439 ui.status(m, label='log.branch')
5440 5440
5441 5441 if marks:
5442 5442 current = repo._bookmarkcurrent
5443 5443 ui.write(_('bookmarks:'), label='log.bookmark')
5444 5444 if current is not None:
5445 5445 try:
5446 5446 marks.remove(current)
5447 5447 ui.write(' *' + current, label='bookmarks.current')
5448 5448 except ValueError:
5449 5449 # current bookmark not in parent ctx marks
5450 5450 pass
5451 5451 for m in marks:
5452 5452 ui.write(' ' + m, label='log.bookmark')
5453 5453 ui.write('\n', label='log.bookmark')
5454 5454
5455 5455 st = list(repo.status(unknown=True))[:6]
5456 5456
5457 5457 c = repo.dirstate.copies()
5458 5458 copied, renamed = [], []
5459 5459 for d, s in c.iteritems():
5460 5460 if s in st[2]:
5461 5461 st[2].remove(s)
5462 5462 renamed.append(d)
5463 5463 else:
5464 5464 copied.append(d)
5465 5465 if d in st[1]:
5466 5466 st[1].remove(d)
5467 5467 st.insert(3, renamed)
5468 5468 st.insert(4, copied)
5469 5469
5470 5470 ms = mergemod.mergestate(repo)
5471 5471 st.append([f for f in ms if ms[f] == 'u'])
5472 5472
5473 5473 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5474 5474 st.append(subs)
5475 5475
5476 5476 labels = [ui.label(_('%d modified'), 'status.modified'),
5477 5477 ui.label(_('%d added'), 'status.added'),
5478 5478 ui.label(_('%d removed'), 'status.removed'),
5479 5479 ui.label(_('%d renamed'), 'status.copied'),
5480 5480 ui.label(_('%d copied'), 'status.copied'),
5481 5481 ui.label(_('%d deleted'), 'status.deleted'),
5482 5482 ui.label(_('%d unknown'), 'status.unknown'),
5483 5483 ui.label(_('%d ignored'), 'status.ignored'),
5484 5484 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5485 5485 ui.label(_('%d subrepos'), 'status.modified')]
5486 5486 t = []
5487 5487 for s, l in zip(st, labels):
5488 5488 if s:
5489 5489 t.append(l % len(s))
5490 5490
5491 5491 t = ', '.join(t)
5492 5492 cleanworkdir = False
5493 5493
5494 5494 if len(parents) > 1:
5495 5495 t += _(' (merge)')
5496 5496 elif branch != parents[0].branch():
5497 5497 t += _(' (new branch)')
5498 5498 elif (parents[0].closesbranch() and
5499 5499 pnode in repo.branchheads(branch, closed=True)):
5500 5500 t += _(' (head closed)')
5501 5501 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5502 5502 t += _(' (clean)')
5503 5503 cleanworkdir = True
5504 5504 elif pnode not in bheads:
5505 5505 t += _(' (new branch head)')
5506 5506
5507 5507 if cleanworkdir:
5508 5508 ui.status(_('commit: %s\n') % t.strip())
5509 5509 else:
5510 5510 ui.write(_('commit: %s\n') % t.strip())
5511 5511
5512 5512 # all ancestors of branch heads - all ancestors of parent = new csets
5513 5513 new = [0] * len(repo)
5514 5514 cl = repo.changelog
5515 5515 for a in [cl.rev(n) for n in bheads]:
5516 5516 new[a] = 1
5517 5517 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5518 5518 new[a] = 1
5519 5519 for a in [p.rev() for p in parents]:
5520 5520 if a >= 0:
5521 5521 new[a] = 0
5522 5522 for a in cl.ancestors([p.rev() for p in parents]):
5523 5523 new[a] = 0
5524 5524 new = sum(new)
5525 5525
5526 5526 if new == 0:
5527 5527 ui.status(_('update: (current)\n'))
5528 5528 elif pnode not in bheads:
5529 5529 ui.write(_('update: %d new changesets (update)\n') % new)
5530 5530 else:
5531 5531 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5532 5532 (new, len(bheads)))
5533 5533
5534 5534 if opts.get('remote'):
5535 5535 t = []
5536 5536 source, branches = hg.parseurl(ui.expandpath('default'))
5537 5537 other = hg.peer(repo, {}, source)
5538 5538 revs, checkout = hg.addbranchrevs(repo, other, branches,
5539 5539 opts.get('rev'))
5540 5540 ui.debug('comparing with %s\n' % util.hidepassword(source))
5541 5541 repo.ui.pushbuffer()
5542 5542 commoninc = discovery.findcommonincoming(repo, other)
5543 5543 _common, incoming, _rheads = commoninc
5544 5544 repo.ui.popbuffer()
5545 5545 if incoming:
5546 5546 t.append(_('1 or more incoming'))
5547 5547
5548 5548 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5549 5549 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5550 5550 if source != dest:
5551 5551 other = hg.peer(repo, {}, dest)
5552 5552 commoninc = None
5553 5553 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5554 5554 repo.ui.pushbuffer()
5555 5555 outgoing = discovery.findcommonoutgoing(repo, other,
5556 5556 commoninc=commoninc)
5557 5557 repo.ui.popbuffer()
5558 5558 o = outgoing.missing
5559 5559 if o:
5560 5560 t.append(_('%d outgoing') % len(o))
5561 5561 if 'bookmarks' in other.listkeys('namespaces'):
5562 5562 lmarks = repo.listkeys('bookmarks')
5563 5563 rmarks = other.listkeys('bookmarks')
5564 5564 diff = set(rmarks) - set(lmarks)
5565 5565 if len(diff) > 0:
5566 5566 t.append(_('%d incoming bookmarks') % len(diff))
5567 5567 diff = set(lmarks) - set(rmarks)
5568 5568 if len(diff) > 0:
5569 5569 t.append(_('%d outgoing bookmarks') % len(diff))
5570 5570
5571 5571 if t:
5572 5572 ui.write(_('remote: %s\n') % (', '.join(t)))
5573 5573 else:
5574 5574 ui.status(_('remote: (synced)\n'))
5575 5575
5576 5576 @command('tag',
5577 5577 [('f', 'force', None, _('force tag')),
5578 5578 ('l', 'local', None, _('make the tag local')),
5579 5579 ('r', 'rev', '', _('revision to tag'), _('REV')),
5580 5580 ('', 'remove', None, _('remove a tag')),
5581 5581 # -l/--local is already there, commitopts cannot be used
5582 5582 ('e', 'edit', None, _('edit commit message')),
5583 5583 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5584 5584 ] + commitopts2,
5585 5585 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5586 5586 def tag(ui, repo, name1, *names, **opts):
5587 5587 """add one or more tags for the current or given revision
5588 5588
5589 5589 Name a particular revision using <name>.
5590 5590
5591 5591 Tags are used to name particular revisions of the repository and are
5592 5592 very useful to compare different revisions, to go back to significant
5593 5593 earlier versions or to mark branch points as releases, etc. Changing
5594 5594 an existing tag is normally disallowed; use -f/--force to override.
5595 5595
5596 5596 If no revision is given, the parent of the working directory is
5597 5597 used, or tip if no revision is checked out.
5598 5598
5599 5599 To facilitate version control, distribution, and merging of tags,
5600 5600 they are stored as a file named ".hgtags" which is managed similarly
5601 5601 to other project files and can be hand-edited if necessary. This
5602 5602 also means that tagging creates a new commit. The file
5603 5603 ".hg/localtags" is used for local tags (not shared among
5604 5604 repositories).
5605 5605
5606 5606 Tag commits are usually made at the head of a branch. If the parent
5607 5607 of the working directory is not a branch head, :hg:`tag` aborts; use
5608 5608 -f/--force to force the tag commit to be based on a non-head
5609 5609 changeset.
5610 5610
5611 5611 See :hg:`help dates` for a list of formats valid for -d/--date.
5612 5612
5613 5613 Since tag names have priority over branch names during revision
5614 5614 lookup, using an existing branch name as a tag name is discouraged.
5615 5615
5616 5616 Returns 0 on success.
5617 5617 """
5618 5618 wlock = lock = None
5619 5619 try:
5620 5620 wlock = repo.wlock()
5621 5621 lock = repo.lock()
5622 5622 rev_ = "."
5623 5623 names = [t.strip() for t in (name1,) + names]
5624 5624 if len(names) != len(set(names)):
5625 5625 raise util.Abort(_('tag names must be unique'))
5626 5626 for n in names:
5627 5627 if n in ['tip', '.', 'null']:
5628 5628 raise util.Abort(_("the name '%s' is reserved") % n)
5629 5629 if not n:
5630 5630 raise util.Abort(_('tag names cannot consist entirely of '
5631 5631 'whitespace'))
5632 5632 if opts.get('rev') and opts.get('remove'):
5633 5633 raise util.Abort(_("--rev and --remove are incompatible"))
5634 5634 if opts.get('rev'):
5635 5635 rev_ = opts['rev']
5636 5636 message = opts.get('message')
5637 5637 if opts.get('remove'):
5638 5638 expectedtype = opts.get('local') and 'local' or 'global'
5639 5639 for n in names:
5640 5640 if not repo.tagtype(n):
5641 5641 raise util.Abort(_("tag '%s' does not exist") % n)
5642 5642 if repo.tagtype(n) != expectedtype:
5643 5643 if expectedtype == 'global':
5644 5644 raise util.Abort(_("tag '%s' is not a global tag") % n)
5645 5645 else:
5646 5646 raise util.Abort(_("tag '%s' is not a local tag") % n)
5647 5647 rev_ = nullid
5648 5648 if not message:
5649 5649 # we don't translate commit messages
5650 5650 message = 'Removed tag %s' % ', '.join(names)
5651 5651 elif not opts.get('force'):
5652 5652 for n in names:
5653 5653 if n in repo.tags():
5654 5654 raise util.Abort(_("tag '%s' already exists "
5655 5655 "(use -f to force)") % n)
5656 5656 if not opts.get('local'):
5657 5657 p1, p2 = repo.dirstate.parents()
5658 5658 if p2 != nullid:
5659 5659 raise util.Abort(_('uncommitted merge'))
5660 5660 bheads = repo.branchheads()
5661 5661 if not opts.get('force') and bheads and p1 not in bheads:
5662 5662 raise util.Abort(_('not at a branch head (use -f to force)'))
5663 5663 r = scmutil.revsingle(repo, rev_).node()
5664 5664
5665 5665 if not message:
5666 5666 # we don't translate commit messages
5667 5667 message = ('Added tag %s for changeset %s' %
5668 5668 (', '.join(names), short(r)))
5669 5669
5670 5670 date = opts.get('date')
5671 5671 if date:
5672 5672 date = util.parsedate(date)
5673 5673
5674 5674 if opts.get('edit'):
5675 5675 message = ui.edit(message, ui.username())
5676 5676
5677 5677 # don't allow tagging the null rev
5678 5678 if (not opts.get('remove') and
5679 5679 scmutil.revsingle(repo, rev_).rev() == nullrev):
5680 5680 raise util.Abort(_("null revision specified"))
5681 5681
5682 5682 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5683 5683 finally:
5684 5684 release(lock, wlock)
5685 5685
5686 5686 @command('tags', [], '')
5687 5687 def tags(ui, repo):
5688 5688 """list repository tags
5689 5689
5690 5690 This lists both regular and local tags. When the -v/--verbose
5691 5691 switch is used, a third column "local" is printed for local tags.
5692 5692
5693 5693 Returns 0 on success.
5694 5694 """
5695 5695
5696 5696 hexfunc = ui.debugflag and hex or short
5697 5697 tagtype = ""
5698 5698
5699 5699 for t, n in reversed(repo.tagslist()):
5700 5700 if ui.quiet:
5701 5701 ui.write("%s\n" % t, label='tags.normal')
5702 5702 continue
5703 5703
5704 5704 hn = hexfunc(n)
5705 5705 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5706 5706 rev = ui.label(r, 'log.changeset')
5707 5707 spaces = " " * (30 - encoding.colwidth(t))
5708 5708
5709 5709 tag = ui.label(t, 'tags.normal')
5710 5710 if ui.verbose:
5711 5711 if repo.tagtype(t) == 'local':
5712 5712 tagtype = " local"
5713 5713 tag = ui.label(t, 'tags.local')
5714 5714 else:
5715 5715 tagtype = ""
5716 5716 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5717 5717
5718 5718 @command('tip',
5719 5719 [('p', 'patch', None, _('show patch')),
5720 5720 ('g', 'git', None, _('use git extended diff format')),
5721 5721 ] + templateopts,
5722 5722 _('[-p] [-g]'))
5723 5723 def tip(ui, repo, **opts):
5724 5724 """show the tip revision
5725 5725
5726 5726 The tip revision (usually just called the tip) is the changeset
5727 5727 most recently added to the repository (and therefore the most
5728 5728 recently changed head).
5729 5729
5730 5730 If you have just made a commit, that commit will be the tip. If
5731 5731 you have just pulled changes from another repository, the tip of
5732 5732 that repository becomes the current tip. The "tip" tag is special
5733 5733 and cannot be renamed or assigned to a different changeset.
5734 5734
5735 5735 Returns 0 on success.
5736 5736 """
5737 5737 displayer = cmdutil.show_changeset(ui, repo, opts)
5738 5738 displayer.show(repo[len(repo) - 1])
5739 5739 displayer.close()
5740 5740
5741 5741 @command('unbundle',
5742 5742 [('u', 'update', None,
5743 5743 _('update to new branch head if changesets were unbundled'))],
5744 5744 _('[-u] FILE...'))
5745 5745 def unbundle(ui, repo, fname1, *fnames, **opts):
5746 5746 """apply one or more changegroup files
5747 5747
5748 5748 Apply one or more compressed changegroup files generated by the
5749 5749 bundle command.
5750 5750
5751 5751 Returns 0 on success, 1 if an update has unresolved files.
5752 5752 """
5753 5753 fnames = (fname1,) + fnames
5754 5754
5755 5755 lock = repo.lock()
5756 5756 wc = repo['.']
5757 5757 try:
5758 5758 for fname in fnames:
5759 5759 f = url.open(ui, fname)
5760 5760 gen = changegroup.readbundle(f, fname)
5761 5761 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5762 5762 finally:
5763 5763 lock.release()
5764 5764 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5765 5765 return postincoming(ui, repo, modheads, opts.get('update'), None)
5766 5766
5767 5767 @command('^update|up|checkout|co',
5768 5768 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5769 5769 ('c', 'check', None,
5770 5770 _('update across branches if no uncommitted changes')),
5771 5771 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5772 5772 ('r', 'rev', '', _('revision'), _('REV'))],
5773 5773 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5774 5774 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5775 5775 """update working directory (or switch revisions)
5776 5776
5777 5777 Update the repository's working directory to the specified
5778 5778 changeset. If no changeset is specified, update to the tip of the
5779 5779 current named branch and move the current bookmark (see :hg:`help
5780 5780 bookmarks`).
5781 5781
5782 5782 Update sets the working directory's parent revision to the specified
5783 5783 changeset (see :hg:`help parents`).
5784 5784
5785 5785 If the changeset is not a descendant or ancestor of the working
5786 5786 directory's parent, the update is aborted. With the -c/--check
5787 5787 option, the working directory is checked for uncommitted changes; if
5788 5788 none are found, the working directory is updated to the specified
5789 5789 changeset.
5790 5790
5791 5791 .. container:: verbose
5792 5792
5793 5793 The following rules apply when the working directory contains
5794 5794 uncommitted changes:
5795 5795
5796 5796 1. If neither -c/--check nor -C/--clean is specified, and if
5797 5797 the requested changeset is an ancestor or descendant of
5798 5798 the working directory's parent, the uncommitted changes
5799 5799 are merged into the requested changeset and the merged
5800 5800 result is left uncommitted. If the requested changeset is
5801 5801 not an ancestor or descendant (that is, it is on another
5802 5802 branch), the update is aborted and the uncommitted changes
5803 5803 are preserved.
5804 5804
5805 5805 2. With the -c/--check option, the update is aborted and the
5806 5806 uncommitted changes are preserved.
5807 5807
5808 5808 3. With the -C/--clean option, uncommitted changes are discarded and
5809 5809 the working directory is updated to the requested changeset.
5810 5810
5811 5811 To cancel an uncommitted merge (and lose your changes), use
5812 5812 :hg:`update --clean .`.
5813 5813
5814 5814 Use null as the changeset to remove the working directory (like
5815 5815 :hg:`clone -U`).
5816 5816
5817 5817 If you want to revert just one file to an older revision, use
5818 5818 :hg:`revert [-r REV] NAME`.
5819 5819
5820 5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5821 5821
5822 5822 Returns 0 on success, 1 if there are unresolved files.
5823 5823 """
5824 5824 if rev and node:
5825 5825 raise util.Abort(_("please specify just one revision"))
5826 5826
5827 5827 if rev is None or rev == '':
5828 5828 rev = node
5829 5829
5830 5830 # with no argument, we also move the current bookmark, if any
5831 5831 movemarkfrom = None
5832 5832 if rev is None or node == '':
5833 5833 movemarkfrom = repo['.'].node()
5834 5834
5835 5835 # if we defined a bookmark, we have to remember the original bookmark name
5836 5836 brev = rev
5837 5837 rev = scmutil.revsingle(repo, rev, rev).rev()
5838 5838
5839 5839 if check and clean:
5840 5840 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5841 5841
5842 5842 if date:
5843 5843 if rev is not None:
5844 5844 raise util.Abort(_("you can't specify a revision and a date"))
5845 5845 rev = cmdutil.finddate(ui, repo, date)
5846 5846
5847 5847 if check:
5848 5848 c = repo[None]
5849 5849 if c.dirty(merge=False, branch=False):
5850 5850 raise util.Abort(_("uncommitted local changes"))
5851 5851 if rev is None:
5852 5852 rev = repo[repo[None].branch()].rev()
5853 5853 mergemod._checkunknown(repo, repo[None], repo[rev])
5854 5854
5855 5855 if clean:
5856 5856 ret = hg.clean(repo, rev)
5857 5857 else:
5858 5858 ret = hg.update(repo, rev)
5859 5859
5860 5860 if not ret and movemarkfrom:
5861 5861 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5862 5862 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5863 5863 elif brev in repo._bookmarks:
5864 5864 bookmarks.setcurrent(repo, brev)
5865 5865 elif brev:
5866 5866 bookmarks.unsetcurrent(repo)
5867 5867
5868 5868 return ret
5869 5869
5870 5870 @command('verify', [])
5871 5871 def verify(ui, repo):
5872 5872 """verify the integrity of the repository
5873 5873
5874 5874 Verify the integrity of the current repository.
5875 5875
5876 5876 This will perform an extensive check of the repository's
5877 5877 integrity, validating the hashes and checksums of each entry in
5878 5878 the changelog, manifest, and tracked files, as well as the
5879 5879 integrity of their crosslinks and indices.
5880 5880
5881 5881 Returns 0 on success, 1 if errors are encountered.
5882 5882 """
5883 5883 return hg.verify(repo)
5884 5884
5885 5885 @command('version', [])
5886 5886 def version_(ui):
5887 5887 """output version and copyright information"""
5888 5888 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5889 5889 % util.version())
5890 5890 ui.status(_(
5891 5891 "(see http://mercurial.selenic.com for more information)\n"
5892 5892 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5893 5893 "This is free software; see the source for copying conditions. "
5894 5894 "There is NO\nwarranty; "
5895 5895 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5896 5896 ))
5897 5897
5898 5898 norepo = ("clone init version help debugcommands debugcomplete"
5899 5899 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5900 5900 " debugknown debuggetbundle debugbundle")
5901 5901 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5902 5902 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,238 +1,238
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import struct
10 10 import sys, os
11 11 import dispatch, encoding, util
12 12
13 13 logfile = None
14 14
15 15 def log(*args):
16 16 if not logfile:
17 17 return
18 18
19 19 for a in args:
20 20 logfile.write(str(a))
21 21
22 22 logfile.flush()
23 23
24 24 class channeledoutput(object):
25 25 """
26 26 Write data from in_ to out in the following format:
27 27
28 28 data length (unsigned int),
29 29 data
30 30 """
31 31 def __init__(self, in_, out, channel):
32 32 self.in_ = in_
33 33 self.out = out
34 34 self.channel = channel
35 35
36 36 def write(self, data):
37 37 if not data:
38 38 return
39 39 self.out.write(struct.pack('>cI', self.channel, len(data)))
40 40 self.out.write(data)
41 41 self.out.flush()
42 42
43 43 def __getattr__(self, attr):
44 44 if attr in ('isatty', 'fileno'):
45 45 raise AttributeError, attr
46 46 return getattr(self.in_, attr)
47 47
48 48 class channeledinput(object):
49 49 """
50 50 Read data from in_.
51 51
52 52 Requests for input are written to out in the following format:
53 53 channel identifier - 'I' for plain input, 'L' line based (1 byte)
54 54 how many bytes to send at most (unsigned int),
55 55
56 56 The client replies with:
57 57 data length (unsigned int), 0 meaning EOF
58 58 data
59 59 """
60 60
61 61 maxchunksize = 4 * 1024
62 62
63 63 def __init__(self, in_, out, channel):
64 64 self.in_ = in_
65 65 self.out = out
66 66 self.channel = channel
67 67
68 68 def read(self, size=-1):
69 69 if size < 0:
70 70 # if we need to consume all the clients input, ask for 4k chunks
71 71 # so the pipe doesn't fill up risking a deadlock
72 72 size = self.maxchunksize
73 73 s = self._read(size, self.channel)
74 74 buf = s
75 75 while s:
76 76 s = self._read(size, self.channel)
77 77 buf += s
78 78
79 79 return buf
80 80 else:
81 81 return self._read(size, self.channel)
82 82
83 83 def _read(self, size, channel):
84 84 if not size:
85 85 return ''
86 86 assert size > 0
87 87
88 88 # tell the client we need at most size bytes
89 89 self.out.write(struct.pack('>cI', channel, size))
90 90 self.out.flush()
91 91
92 92 length = self.in_.read(4)
93 93 length = struct.unpack('>I', length)[0]
94 94 if not length:
95 95 return ''
96 96 else:
97 97 return self.in_.read(length)
98 98
99 99 def readline(self, size=-1):
100 100 if size < 0:
101 101 size = self.maxchunksize
102 102 s = self._read(size, 'L')
103 103 buf = s
104 104 # keep asking for more until there's either no more or
105 105 # we got a full line
106 106 while s and s[-1] != '\n':
107 107 s = self._read(size, 'L')
108 108 buf += s
109 109
110 110 return buf
111 111 else:
112 112 return self._read(size, 'L')
113 113
114 114 def __iter__(self):
115 115 return self
116 116
117 117 def next(self):
118 118 l = self.readline()
119 119 if not l:
120 120 raise StopIteration
121 121 return l
122 122
123 123 def __getattr__(self, attr):
124 124 if attr in ('isatty', 'fileno'):
125 125 raise AttributeError, attr
126 126 return getattr(self.in_, attr)
127 127
128 128 class server(object):
129 129 """
130 130 Listens for commands on stdin, runs them and writes the output on a channel
131 131 based stream to stdout.
132 132 """
133 133 def __init__(self, ui, repo, mode):
134 134 self.cwd = os.getcwd()
135 135
136 136 logpath = ui.config("cmdserver", "log", None)
137 137 if logpath:
138 138 global logfile
139 139 if logpath == '-':
140 # write log on a special 'd'ebug channel
140 # write log on a special 'd' (debug) channel
141 141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
142 142 else:
143 143 logfile = open(logpath, 'a')
144 144
145 145 # the ui here is really the repo ui so take its baseui so we don't end
146 146 # up with its local configuration
147 147 self.ui = repo.baseui
148 148 self.repo = repo
149 149 self.repoui = repo.ui
150 150
151 151 if mode == 'pipe':
152 152 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
153 153 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
154 154 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
155 155 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
156 156
157 157 self.client = sys.stdin
158 158 else:
159 159 raise util.Abort(_('unknown mode %s') % mode)
160 160
161 161 def _read(self, size):
162 162 if not size:
163 163 return ''
164 164
165 165 data = self.client.read(size)
166 166
167 167 # is the other end closed?
168 168 if not data:
169 169 raise EOFError
170 170
171 171 return data
172 172
173 173 def runcommand(self):
174 174 """ reads a list of \0 terminated arguments, executes
175 175 and writes the return code to the result channel """
176 176
177 177 length = struct.unpack('>I', self._read(4))[0]
178 178 if not length:
179 179 args = []
180 180 else:
181 181 args = self._read(length).split('\0')
182 182
183 183 # copy the uis so changes (e.g. --config or --verbose) don't
184 184 # persist between requests
185 185 copiedui = self.ui.copy()
186 186 self.repo.baseui = copiedui
187 187 self.repo.ui = self.repo.dirstate._ui = self.repoui.copy()
188 188 self.repo.invalidate()
189 189 self.repo.invalidatedirstate()
190 190
191 191 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
192 192 self.cout, self.cerr)
193 193
194 194 ret = dispatch.dispatch(req) or 0 # might return None
195 195
196 196 # restore old cwd
197 197 if '--cwd' in args:
198 198 os.chdir(self.cwd)
199 199
200 200 self.cresult.write(struct.pack('>i', int(ret)))
201 201
202 202 def getencoding(self):
203 203 """ writes the current encoding to the result channel """
204 204 self.cresult.write(encoding.encoding)
205 205
206 206 def serveone(self):
207 207 cmd = self.client.readline()[:-1]
208 208 if cmd:
209 209 handler = self.capabilities.get(cmd)
210 210 if handler:
211 211 handler(self)
212 212 else:
213 213 # clients are expected to check what commands are supported by
214 214 # looking at the servers capabilities
215 215 raise util.Abort(_('unknown command %s') % cmd)
216 216
217 217 return cmd != ''
218 218
219 219 capabilities = {'runcommand' : runcommand,
220 220 'getencoding' : getencoding}
221 221
222 222 def serve(self):
223 223 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
224 224 hellomsg += '\n'
225 225 hellomsg += 'encoding: ' + encoding.encoding
226 226
227 227 # write the hello msg in -one- chunk
228 228 self.cout.write(hellomsg)
229 229
230 230 try:
231 231 while self.serveone():
232 232 pass
233 233 except EOFError:
234 234 # we'll get here if the client disconnected while we were reading
235 235 # its request
236 236 return 1
237 237
238 238 return 0
@@ -1,183 +1,183
1 1 # config.py - configuration parsing for Mercurial
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import error, util
10 10 import os, errno
11 11
12 12 class sortdict(dict):
13 13 'a simple sorted dictionary'
14 14 def __init__(self, data=None):
15 15 self._list = []
16 16 if data:
17 17 self.update(data)
18 18 def copy(self):
19 19 return sortdict(self)
20 20 def __setitem__(self, key, val):
21 21 if key in self:
22 22 self._list.remove(key)
23 23 self._list.append(key)
24 24 dict.__setitem__(self, key, val)
25 25 def __iter__(self):
26 26 return self._list.__iter__()
27 27 def update(self, src):
28 28 for k in src:
29 29 self[k] = src[k]
30 30 def clear(self):
31 31 dict.clear(self)
32 32 self._list = []
33 33 def items(self):
34 34 return [(k, self[k]) for k in self._list]
35 35 def __delitem__(self, key):
36 36 dict.__delitem__(self, key)
37 37 self._list.remove(key)
38 38 def keys(self):
39 39 return self._list
40 40 def iterkeys(self):
41 41 return self._list.__iter__()
42 42
43 43 class config(object):
44 44 def __init__(self, data=None):
45 45 self._data = {}
46 46 self._source = {}
47 47 if data:
48 48 for k in data._data:
49 49 self._data[k] = data[k].copy()
50 50 self._source = data._source.copy()
51 51 def copy(self):
52 52 return config(self)
53 53 def __contains__(self, section):
54 54 return section in self._data
55 55 def __getitem__(self, section):
56 56 return self._data.get(section, {})
57 57 def __iter__(self):
58 58 for d in self.sections():
59 59 yield d
60 60 def update(self, src):
61 61 for s in src:
62 62 if s not in self:
63 63 self._data[s] = sortdict()
64 64 self._data[s].update(src._data[s])
65 65 self._source.update(src._source)
66 66 def get(self, section, item, default=None):
67 67 return self._data.get(section, {}).get(item, default)
68 68
69 69 def backup(self, section, item):
70 """return a tuple allowing restore to reinstall a previous valuesi
70 """return a tuple allowing restore to reinstall previous values
71 71
72 72 The main reason we need it is because it handle the "no data" case.
73 73 """
74 74 try:
75 75 value = self._data[section][item]
76 76 source = self.source(section, item)
77 77 return (section, item, value, source)
78 78 except KeyError:
79 79 return (section, item)
80 80
81 81 def source(self, section, item):
82 82 return self._source.get((section, item), "")
83 83 def sections(self):
84 84 return sorted(self._data.keys())
85 85 def items(self, section):
86 86 return self._data.get(section, {}).items()
87 87 def set(self, section, item, value, source=""):
88 88 if section not in self:
89 89 self._data[section] = sortdict()
90 90 self._data[section][item] = value
91 91 self._source[(section, item)] = source
92 92
93 93 def restore(self, data):
94 94 """restore data returned by self.backup"""
95 95 if len(data) == 4:
96 96 # restore old data
97 97 section, item, value, source = data
98 98 self._data[section][item] = value
99 99 self._source[(section, item)] = source
100 100 else:
101 101 # no data before, remove everything
102 102 section, item = data
103 103 if section in self._data:
104 104 del self._data[section][item]
105 105 self._source.pop((section, item), None)
106 106
107 107 def parse(self, src, data, sections=None, remap=None, include=None):
108 108 sectionre = util.compilere(r'\[([^\[]+)\]')
109 109 itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
110 110 contre = util.compilere(r'\s+(\S|\S.*\S)\s*$')
111 111 emptyre = util.compilere(r'(;|#|\s*$)')
112 112 commentre = util.compilere(r'(;|#)')
113 113 unsetre = util.compilere(r'%unset\s+(\S+)')
114 114 includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$')
115 115 section = ""
116 116 item = None
117 117 line = 0
118 118 cont = False
119 119
120 120 for l in data.splitlines(True):
121 121 line += 1
122 122 if line == 1 and l.startswith('\xef\xbb\xbf'):
123 123 # Someone set us up the BOM
124 124 l = l[3:]
125 125 if cont:
126 126 if commentre.match(l):
127 127 continue
128 128 m = contre.match(l)
129 129 if m:
130 130 if sections and section not in sections:
131 131 continue
132 132 v = self.get(section, item) + "\n" + m.group(1)
133 133 self.set(section, item, v, "%s:%d" % (src, line))
134 134 continue
135 135 item = None
136 136 cont = False
137 137 m = includere.match(l)
138 138 if m:
139 139 inc = util.expandpath(m.group(1))
140 140 base = os.path.dirname(src)
141 141 inc = os.path.normpath(os.path.join(base, inc))
142 142 if include:
143 143 try:
144 144 include(inc, remap=remap, sections=sections)
145 145 except IOError, inst:
146 146 if inst.errno != errno.ENOENT:
147 147 raise error.ParseError(_("cannot include %s (%s)")
148 148 % (inc, inst.strerror),
149 149 "%s:%s" % (src, line))
150 150 continue
151 151 if emptyre.match(l):
152 152 continue
153 153 m = sectionre.match(l)
154 154 if m:
155 155 section = m.group(1)
156 156 if remap:
157 157 section = remap.get(section, section)
158 158 if section not in self:
159 159 self._data[section] = sortdict()
160 160 continue
161 161 m = itemre.match(l)
162 162 if m:
163 163 item = m.group(1)
164 164 cont = True
165 165 if sections and section not in sections:
166 166 continue
167 167 self.set(section, item, m.group(2), "%s:%d" % (src, line))
168 168 continue
169 169 m = unsetre.match(l)
170 170 if m:
171 171 name = m.group(1)
172 172 if sections and section not in sections:
173 173 continue
174 174 if self.get(section, name) is not None:
175 175 del self._data[section][name]
176 176 continue
177 177
178 178 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
179 179
180 180 def read(self, path, fp=None, sections=None, remap=None):
181 181 if not fp:
182 182 fp = util.posixfile(path)
183 183 self.parse(path, fp.read(), sections, remap, self.read)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now